mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 04:38:00 +00:00
Compare commits
5247 Commits
forge-1.6.
...
forge-1.6.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5192fc3f59 | ||
|
|
efca55083c | ||
|
|
735a7997d6 | ||
|
|
3e5636ea21 | ||
|
|
5f5bcc35ad | ||
|
|
be5c3a0401 | ||
|
|
17062eecdb | ||
|
|
e036465ce3 | ||
|
|
c52f8efa11 | ||
|
|
183a671227 | ||
|
|
7cc247c6c7 | ||
|
|
a8a69d2a66 | ||
|
|
63822c16c1 | ||
|
|
f577523dee | ||
|
|
dca93282f3 | ||
|
|
0e9bf4fe4b | ||
|
|
7cdf7538e9 | ||
|
|
2ab59849a8 | ||
|
|
60f1b13110 | ||
|
|
de5d7c4349 | ||
|
|
2f30b14c53 | ||
|
|
c1df1da195 | ||
|
|
9bbd1da0ba | ||
|
|
fc8142db8b | ||
|
|
848df654fa | ||
|
|
9ab50c4d9d | ||
|
|
32ab0aadb0 | ||
|
|
c8ab4ff0c4 | ||
|
|
b125dd9e2d | ||
|
|
0e398d2589 | ||
|
|
08e3f1ebd5 | ||
|
|
d273a35305 | ||
|
|
0663024acf | ||
|
|
3f675c0102 | ||
|
|
527740c493 | ||
|
|
231c028d42 | ||
|
|
b8254b6dd9 | ||
|
|
64fe052c0f | ||
|
|
593218343c | ||
|
|
79267ac29f | ||
|
|
701418ab63 | ||
|
|
eefc6ea9d3 | ||
|
|
970931bcfd | ||
|
|
7781660c11 | ||
|
|
e6af6abfcf | ||
|
|
848b495b52 | ||
|
|
bd2b3fab9b | ||
|
|
5d53321885 | ||
|
|
5a5e1ddac8 | ||
|
|
12da68cd77 | ||
|
|
4f3fe32f89 | ||
|
|
6ed4da392e | ||
|
|
b7f5d6e85e | ||
|
|
24e1affd51 | ||
|
|
f0d0949ac5 | ||
|
|
c3371cf5fa | ||
|
|
c699644a74 | ||
|
|
acadff3e04 | ||
|
|
ac940a6db8 | ||
|
|
f6bce3cdb1 | ||
|
|
ab58ded0a1 | ||
|
|
84d3ae83f1 | ||
|
|
52893e54ee | ||
|
|
df2028e4f3 | ||
|
|
8e45262a7a | ||
|
|
ef97991237 | ||
|
|
1d89f466ab | ||
|
|
760c45f9c2 | ||
|
|
bbf63d9083 | ||
|
|
781400c0a3 | ||
|
|
9561b75f17 | ||
|
|
f624d76301 | ||
|
|
35b3bc0623 | ||
|
|
939cdb6822 | ||
|
|
e4303df238 | ||
|
|
2e09c8230b | ||
|
|
c69c81fcd0 | ||
|
|
781aa97546 | ||
|
|
321c6e52c8 | ||
|
|
2d153d58cb | ||
|
|
a46b05d703 | ||
|
|
faa1b1a767 | ||
|
|
4508d54543 | ||
|
|
65e8fd1842 | ||
|
|
fc082f77af | ||
|
|
2435767504 | ||
|
|
744c766852 | ||
|
|
07e43131c2 | ||
|
|
a1e87417f0 | ||
|
|
15cd7c4eab | ||
|
|
de224e78fc | ||
|
|
d4324ae9e1 | ||
|
|
a7041fc767 | ||
|
|
600f52537e | ||
|
|
72f28b6c44 | ||
|
|
43af9df723 | ||
|
|
1dcf66175b | ||
|
|
deff49452c | ||
|
|
0d7328da92 | ||
|
|
b3750d98d4 | ||
|
|
a702da5d2f | ||
|
|
12236efa70 | ||
|
|
57a24ae697 | ||
|
|
2cd2ae6968 | ||
|
|
3674ebff1b | ||
|
|
3b2fd67257 | ||
|
|
b6b270f979 | ||
|
|
54f1fdb453 | ||
|
|
3ceec24a86 | ||
|
|
0f8d711adf | ||
|
|
8a0fc39d16 | ||
|
|
a69cdf37ae | ||
|
|
64907dda5c | ||
|
|
80ed37033b | ||
|
|
b1cd294052 | ||
|
|
2d1bd4d541 | ||
|
|
a28c010b59 | ||
|
|
c72c288dcd | ||
|
|
e4c7b5fe93 | ||
|
|
f322dddbd2 | ||
|
|
b462d76f70 | ||
|
|
a1c234b41e | ||
|
|
5a3cea6bf2 | ||
|
|
643dfc5092 | ||
|
|
95b0003ae3 | ||
|
|
e32f311051 | ||
|
|
888e2c7376 | ||
|
|
0d80650506 | ||
|
|
b50f44d10c | ||
|
|
9a3432e47d | ||
|
|
ee97dd44db | ||
|
|
2bb6b9e715 | ||
|
|
fd122d392d | ||
|
|
ea4063fe2f | ||
|
|
b492d61d2b | ||
|
|
c1fe1de82a | ||
|
|
fb39bce069 | ||
|
|
d87e63c9a7 | ||
|
|
8e0e976681 | ||
|
|
9d9557009c | ||
|
|
48827f34ee | ||
|
|
a710d862bb | ||
|
|
664f95bc69 | ||
|
|
2ab7b95c47 | ||
|
|
f4a3fb7daf | ||
|
|
4192f9327e | ||
|
|
5c6fe93422 | ||
|
|
c2359a512f | ||
|
|
45a42ea816 | ||
|
|
c69e7dce8d | ||
|
|
c2b8b21a1e | ||
|
|
d1d9910b67 | ||
|
|
765a15fa6f | ||
|
|
5d3b0f5f7b | ||
|
|
5e3bdc18b2 | ||
|
|
c528db302b | ||
|
|
088891604f | ||
|
|
f6111b6c85 | ||
|
|
ebd58560ec | ||
|
|
5b07e961ac | ||
|
|
719e5c9a08 | ||
|
|
b02a1a9976 | ||
|
|
81f6d6c16e | ||
|
|
09ad92e3c1 | ||
|
|
237f64226f | ||
|
|
c5d5e08c44 | ||
|
|
b7b302c4f9 | ||
|
|
3277377ed6 | ||
|
|
ee9f79a514 | ||
|
|
c3579709ee | ||
|
|
ae96dadd54 | ||
|
|
5503897911 | ||
|
|
2433cf3dad | ||
|
|
5eba12b029 | ||
|
|
c1f3a49ea0 | ||
|
|
bb9cf0903b | ||
|
|
9354182ba4 | ||
|
|
5a500b857b | ||
|
|
27237070ae | ||
|
|
8c13c526a0 | ||
|
|
dedbfbf7b3 | ||
|
|
88551a2107 | ||
|
|
82f1291aa6 | ||
|
|
c03004adaf | ||
|
|
a9e100cac5 | ||
|
|
b48bea304d | ||
|
|
6899fb373f | ||
|
|
6d599d2b8c | ||
|
|
8b6183582a | ||
|
|
612165ba25 | ||
|
|
6f8ff8200c | ||
|
|
6b6629c01c | ||
|
|
e1d34ef9d5 | ||
|
|
71397ec648 | ||
|
|
27d2ba384d | ||
|
|
37cbd2fe64 | ||
|
|
85c1ca232e | ||
|
|
efd5efb0d5 | ||
|
|
aec4726aa2 | ||
|
|
2b3d1727ea | ||
|
|
d70778b5be | ||
|
|
f519a2ff6d | ||
|
|
bb76b63f82 | ||
|
|
5c723d8883 | ||
|
|
933280ce32 | ||
|
|
841cd22ae4 | ||
|
|
d011f9948d | ||
|
|
1da18cb9f5 | ||
|
|
a6423b82f0 | ||
|
|
fc62a41e46 | ||
|
|
2a08ea7092 | ||
|
|
977f3c2808 | ||
|
|
2853e44d6a | ||
|
|
b1bae53be8 | ||
|
|
65dad45e77 | ||
|
|
e36245b414 | ||
|
|
568104bbcc | ||
|
|
5b02986586 | ||
|
|
0f5b1c1349 | ||
|
|
998176a7d7 | ||
|
|
1cac53b4f0 | ||
|
|
b18a99c2c5 | ||
|
|
8adfe15b4c | ||
|
|
9e0a4ded01 | ||
|
|
c57bb0c349 | ||
|
|
fee2312bc8 | ||
|
|
3985d538ae | ||
|
|
58a1ad2dd0 | ||
|
|
4413664b38 | ||
|
|
3b6baa65ad | ||
|
|
ac3c6c3d21 | ||
|
|
6bca50b1c8 | ||
|
|
bd8fea4616 | ||
|
|
55f1e1d41a | ||
|
|
df91964573 | ||
|
|
33d7a0adcc | ||
|
|
66fa0c5820 | ||
|
|
749eea6f57 | ||
|
|
f0830c8595 | ||
|
|
6a4423a476 | ||
|
|
0577059909 | ||
|
|
3fcb03d3ed | ||
|
|
5ab2efdc05 | ||
|
|
c4cf7d3c99 | ||
|
|
5472421765 | ||
|
|
e5281987a5 | ||
|
|
78b02c08db | ||
|
|
29263d2e2d | ||
|
|
3f6a02b4b8 | ||
|
|
2a48819d2b | ||
|
|
c4683677b3 | ||
|
|
cad9139e9d | ||
|
|
5c8acadb5d | ||
|
|
0c38c6306f | ||
|
|
1fa91ecb0f | ||
|
|
11d53bce48 | ||
|
|
27c9fbc0c9 | ||
|
|
7231700ede | ||
|
|
810d55931d | ||
|
|
e4b146128c | ||
|
|
353dc885c1 | ||
|
|
c54ca4c423 | ||
|
|
32df03d045 | ||
|
|
d8f5b870a7 | ||
|
|
61a627a36a | ||
|
|
31b1781178 | ||
|
|
d0640191e3 | ||
|
|
9d04e42a3a | ||
|
|
0baaef6143 | ||
|
|
5e08fd7082 | ||
|
|
e7d09e8918 | ||
|
|
c2f62998fc | ||
|
|
19feee4de5 | ||
|
|
4c816fe4af | ||
|
|
c136e7c946 | ||
|
|
7ebd5ef70e | ||
|
|
e59015357c | ||
|
|
31e2d434cd | ||
|
|
950fd116e7 | ||
|
|
8818711c0f | ||
|
|
58ee5a12eb | ||
|
|
51d49735c2 | ||
|
|
925ae63071 | ||
|
|
d07c4d19e0 | ||
|
|
adf48de86b | ||
|
|
01c34d498a | ||
|
|
4188559b1a | ||
|
|
10a68e9613 | ||
|
|
89c4042042 | ||
|
|
d3fb9f3782 | ||
|
|
3fc1950a19 | ||
|
|
d5d1320c6e | ||
|
|
5ffcfe52d8 | ||
|
|
8a1989ef02 | ||
|
|
b0e3ff7b45 | ||
|
|
f0c850aa78 | ||
|
|
af76c37cbb | ||
|
|
be32b44feb | ||
|
|
e3b3db7471 | ||
|
|
dc8e811352 | ||
|
|
50a2ee70b6 | ||
|
|
3802b88487 | ||
|
|
351d15701f | ||
|
|
64b5b143bf | ||
|
|
36132359a7 | ||
|
|
7dedaeb111 | ||
|
|
8338f62603 | ||
|
|
e4e33ebac0 | ||
|
|
e601a6deae | ||
|
|
d220f5fb78 | ||
|
|
67edb87ccd | ||
|
|
780902ac31 | ||
|
|
1f001dd354 | ||
|
|
d0cea5151e | ||
|
|
6540153a53 | ||
|
|
637ef1e03a | ||
|
|
96459652a1 | ||
|
|
35c52e0e61 | ||
|
|
a664b7dad9 | ||
|
|
6d7c6f2153 | ||
|
|
1c517356cf | ||
|
|
0baf5ca772 | ||
|
|
1a58970c73 | ||
|
|
b370882b86 | ||
|
|
19be7f51ae | ||
|
|
efcee72780 | ||
|
|
0e2f47dc8c | ||
|
|
9b6f76eb16 | ||
|
|
cc044978fb | ||
|
|
05aac0e1cb | ||
|
|
881bd7adf2 | ||
|
|
b0e2450a0e | ||
|
|
6c99a911f2 | ||
|
|
4b1de970f9 | ||
|
|
e98431e525 | ||
|
|
7d6e8e95d3 | ||
|
|
b56b90ec1a | ||
|
|
5a81be04a6 | ||
|
|
ab209d6db5 | ||
|
|
724e4d5932 | ||
|
|
6ec0dd49d7 | ||
|
|
c67b10dee6 | ||
|
|
a1ebb1f3df | ||
|
|
f5add1bc8f | ||
|
|
67aaf75973 | ||
|
|
952e08848c | ||
|
|
2ab982463e | ||
|
|
43338bd0a3 | ||
|
|
2476bcfa31 | ||
|
|
f967d9885a | ||
|
|
83721cadbd | ||
|
|
b22c1ece86 | ||
|
|
73b94a63a5 | ||
|
|
2bcd57910e | ||
|
|
b279ae0771 | ||
|
|
51735b9ff7 | ||
|
|
2f1527d89f | ||
|
|
0ded352f22 | ||
|
|
66e52cbd6a | ||
|
|
aec894a569 | ||
|
|
db7ab85f26 | ||
|
|
5f6ea460dd | ||
|
|
97f93418bf | ||
|
|
42acde1ba0 | ||
|
|
095bb4a154 | ||
|
|
e70f6da283 | ||
|
|
97e6657bfc | ||
|
|
071c45839d | ||
|
|
39c002e903 | ||
|
|
57d5013750 | ||
|
|
11ca1231a4 | ||
|
|
9acb1dca93 | ||
|
|
5fec8ed29d | ||
|
|
3dda238f7a | ||
|
|
017f84c056 | ||
|
|
97d4fef0bf | ||
|
|
c5d853d5af | ||
|
|
f7c7159942 | ||
|
|
eaeb2838e0 | ||
|
|
f500f6837f | ||
|
|
a9292924ae | ||
|
|
fb5710eeec | ||
|
|
65282bee6a | ||
|
|
0a3532d4c5 | ||
|
|
f217b3da3a | ||
|
|
83254033fb | ||
|
|
2c3e7257c7 | ||
|
|
746c8b7d8f | ||
|
|
367875c15c | ||
|
|
1254a03782 | ||
|
|
85a386d58d | ||
|
|
afc3182aa0 | ||
|
|
e968afbd87 | ||
|
|
95e9a0bcc8 | ||
|
|
78a090dfd9 | ||
|
|
2603d14b7e | ||
|
|
ed04025b3b | ||
|
|
858642510f | ||
|
|
abb1ec890a | ||
|
|
04202f0fb4 | ||
|
|
68e40254f8 | ||
|
|
fb3e0f1445 | ||
|
|
3aa5d191fa | ||
|
|
0f3ac8c212 | ||
|
|
5f385f8a40 | ||
|
|
baf4a17ceb | ||
|
|
e2c33b089c | ||
|
|
97d0e4f1b7 | ||
|
|
63c06ed7cb | ||
|
|
f7230cbff3 | ||
|
|
8627ecb165 | ||
|
|
553a794642 | ||
|
|
bf3ce5b68a | ||
|
|
48a7e50bfa | ||
|
|
f951e831c0 | ||
|
|
226754d80f | ||
|
|
a72dfcacea | ||
|
|
22766f9942 | ||
|
|
b927c790c7 | ||
|
|
1ae10cb5ca | ||
|
|
b19eca2aff | ||
|
|
4a95685436 | ||
|
|
ab959890b8 | ||
|
|
d2f5bd1a3d | ||
|
|
292ba47f70 | ||
|
|
c026c399c3 | ||
|
|
c13aebe791 | ||
|
|
f1f96557c2 | ||
|
|
cdf9e3693d | ||
|
|
4cdc7b62ed | ||
|
|
5817a795f3 | ||
|
|
0c5cab8d12 | ||
|
|
8c1f4c5720 | ||
|
|
4ddfe40ff1 | ||
|
|
7be9ef06e0 | ||
|
|
3dfa54e4da | ||
|
|
58c1bd35a9 | ||
|
|
9c517070e4 | ||
|
|
75caf0643a | ||
|
|
41ce31497b | ||
|
|
fa1128b94c | ||
|
|
e147900618 | ||
|
|
28c2aceba3 | ||
|
|
c45cf8df10 | ||
|
|
a0803cfce6 | ||
|
|
385fa74c6e | ||
|
|
2aa8d4bb1a | ||
|
|
269d6ead0c | ||
|
|
b980bbf66e | ||
|
|
e236202ddb | ||
|
|
2174672511 | ||
|
|
75ebadac10 | ||
|
|
2e994a4e89 | ||
|
|
95568d2975 | ||
|
|
e37a470127 | ||
|
|
8e80c8c5f5 | ||
|
|
4e17dcc332 | ||
|
|
7bf6aa7f6e | ||
|
|
36bf0344b0 | ||
|
|
739d3abfe6 | ||
|
|
2be7a22217 | ||
|
|
571029508f | ||
|
|
d1945e2c33 | ||
|
|
eff8720500 | ||
|
|
780bae107b | ||
|
|
331d173a93 | ||
|
|
1038dbbe0d | ||
|
|
7ac6bccf9f | ||
|
|
cb5222c997 | ||
|
|
b91261bc06 | ||
|
|
49db080e06 | ||
|
|
131c7d5a75 | ||
|
|
09bfe089de | ||
|
|
347707f74e | ||
|
|
03d66b9ea1 | ||
|
|
a9ade92946 | ||
|
|
3c4b81f2ac | ||
|
|
5c0e70aa36 | ||
|
|
d5248e2d26 | ||
|
|
def2c3ed48 | ||
|
|
a98fdff570 | ||
|
|
197880dda4 | ||
|
|
08d984a699 | ||
|
|
a56476645f | ||
|
|
bb34d29a44 | ||
|
|
1dc2abb7a6 | ||
|
|
e59cd59ea2 | ||
|
|
1d02087128 | ||
|
|
c106d856c0 | ||
|
|
8d2668257c | ||
|
|
b0ce5a2a28 | ||
|
|
7c32151d1f | ||
|
|
6a7584b3f2 | ||
|
|
bef375650f | ||
|
|
30fadb5912 | ||
|
|
91ebc8549c | ||
|
|
0a2dd16d83 | ||
|
|
9815161bb0 | ||
|
|
3d422076ad | ||
|
|
ddb99c4cb5 | ||
|
|
9127dc5d1e | ||
|
|
0876633990 | ||
|
|
943e9836cd | ||
|
|
b543f1b4d2 | ||
|
|
600e8629f4 | ||
|
|
308ecd0be8 | ||
|
|
becd02c8e2 | ||
|
|
be18d49de7 | ||
|
|
ab77d97e59 | ||
|
|
c05464585e | ||
|
|
87c0ab7b7d | ||
|
|
aea32aee62 | ||
|
|
6e8e28a615 | ||
|
|
9ca15ae635 | ||
|
|
bb5102e311 | ||
|
|
4d9b98bc94 | ||
|
|
1e1640b292 | ||
|
|
1b47dce3e0 | ||
|
|
6a340b9004 | ||
|
|
e250077839 | ||
|
|
5e4c7bce82 | ||
|
|
604010fbce | ||
|
|
4b4bab6086 | ||
|
|
7c38b97540 | ||
|
|
1fa768bb81 | ||
|
|
eda2ba4adf | ||
|
|
001a51a070 | ||
|
|
ac4d476ab3 | ||
|
|
c41c3bd161 | ||
|
|
e2e0123d2b | ||
|
|
7e0ebef57b | ||
|
|
4f53188b94 | ||
|
|
653cbd7345 | ||
|
|
3e980f30aa | ||
|
|
9acc59232b | ||
|
|
33d7596cac | ||
|
|
bded1d604a | ||
|
|
fd1fe2b0e4 | ||
|
|
b5ccfb11e7 | ||
|
|
d35af0a89d | ||
|
|
2462ce58ab | ||
|
|
1d57c1a444 | ||
|
|
3d55461332 | ||
|
|
cdb2474fa1 | ||
|
|
49e5cf7be0 | ||
|
|
3b6ab7e8c1 | ||
|
|
0d15166137 | ||
|
|
1d273d1c70 | ||
|
|
ba540a73ec | ||
|
|
69de2ef5c5 | ||
|
|
b624832293 | ||
|
|
d11bafd244 | ||
|
|
6256a157ab | ||
|
|
43a12342e9 | ||
|
|
3d85051f7f | ||
|
|
c83279ad1f | ||
|
|
e65dcbe6d5 | ||
|
|
5e16d52ce9 | ||
|
|
8331660825 | ||
|
|
fc3cd818b2 | ||
|
|
19ca07e65d | ||
|
|
e903cfc32c | ||
|
|
5be840ab1d | ||
|
|
5567c32a4a | ||
|
|
d3db01cf69 | ||
|
|
1379e06678 | ||
|
|
e4c4a87d45 | ||
|
|
8f10afac00 | ||
|
|
8f8493bd47 | ||
|
|
0d7081a119 | ||
|
|
2ae2f90f5b | ||
|
|
098790cbf3 | ||
|
|
448f723c2c | ||
|
|
924e5bdaf1 | ||
|
|
da81748f54 | ||
|
|
422f8d2c80 | ||
|
|
157aa30918 | ||
|
|
ee87a75621 | ||
|
|
6852748739 | ||
|
|
63bca8ce93 | ||
|
|
0d6fbb60ca | ||
|
|
a670245b3d | ||
|
|
9b27742a5a | ||
|
|
ca7f551af4 | ||
|
|
61040f4c05 | ||
|
|
421da7285c | ||
|
|
6d829db99e | ||
|
|
60d247503e | ||
|
|
50cea02ae1 | ||
|
|
05c3402817 | ||
|
|
34c29be136 | ||
|
|
efa8fa10d6 | ||
|
|
8f93f8e4bc | ||
|
|
fd061354ff | ||
|
|
b7553385f1 | ||
|
|
805048b6b8 | ||
|
|
85ad10a78a | ||
|
|
2cc5bc11c9 | ||
|
|
88ac89a6d2 | ||
|
|
3bb9e96dd0 | ||
|
|
36b62f2219 | ||
|
|
7a52668418 | ||
|
|
22efcf2f3f | ||
|
|
1b95e2a23f | ||
|
|
2131bccae7 | ||
|
|
5c90770e6f | ||
|
|
3adf8a21c9 | ||
|
|
2c049b323a | ||
|
|
c331a045cc | ||
|
|
8a0446add1 | ||
|
|
52190b9175 | ||
|
|
a484b5288e | ||
|
|
483a01b936 | ||
|
|
c21f2203c5 | ||
|
|
7d56940384 | ||
|
|
c19832a559 | ||
|
|
07a0e08730 | ||
|
|
5329fb0b51 | ||
|
|
fc2b5f45a2 | ||
|
|
86ab628cce | ||
|
|
8db68d8717 | ||
|
|
40623420f9 | ||
|
|
d6be6f8b43 | ||
|
|
434acedb8f | ||
|
|
165c95a5d8 | ||
|
|
04dc886c54 | ||
|
|
968407b169 | ||
|
|
8134d72861 | ||
|
|
69e5e4cb21 | ||
|
|
1319e0fda9 | ||
|
|
674d09c06d | ||
|
|
f0833573be | ||
|
|
a2d59cae6a | ||
|
|
53eec599c4 | ||
|
|
e70fa161b4 | ||
|
|
a7a6a3265a | ||
|
|
955c79b640 | ||
|
|
253a4d389c | ||
|
|
7d608ece67 | ||
|
|
e572b4a506 | ||
|
|
0b23d89783 | ||
|
|
5eab3065bc | ||
|
|
5a5cfb30ef | ||
|
|
5b3c22201b | ||
|
|
14ee2f99c8 | ||
|
|
a760901a79 | ||
|
|
2cf101ac7d | ||
|
|
48f0d5b5e4 | ||
|
|
eaaa18b15e | ||
|
|
613bae436d | ||
|
|
3c2f5b2ecf | ||
|
|
15aa393d19 | ||
|
|
f8fda3fb2c | ||
|
|
442263710b | ||
|
|
41013330fc | ||
|
|
64b456827d | ||
|
|
54ad6393ac | ||
|
|
c4a3e93e44 | ||
|
|
d51b9cfa24 | ||
|
|
7250fa7134 | ||
|
|
91b60f1c0f | ||
|
|
3e87e8bb55 | ||
|
|
dbe6f2714f | ||
|
|
25220a46ee | ||
|
|
ff3a0fffea | ||
|
|
c9f71ec6f2 | ||
|
|
6bc8aabb6b | ||
|
|
f577e5446a | ||
|
|
d18f891e8f | ||
|
|
ca6514fa45 | ||
|
|
14043d88e5 | ||
|
|
73234133b1 | ||
|
|
5436aa10f0 | ||
|
|
87ffcbef1c | ||
|
|
4d297385a7 | ||
|
|
6e5ebd50ba | ||
|
|
9020530a74 | ||
|
|
44f19b13a7 | ||
|
|
a30aa5d873 | ||
|
|
4d611cc0b9 | ||
|
|
918f0844eb | ||
|
|
4f56595003 | ||
|
|
42dcd45392 | ||
|
|
f43cc09b6f | ||
|
|
8f4eb8596d | ||
|
|
5459914156 | ||
|
|
ecce77bbc6 | ||
|
|
dccfd3ce08 | ||
|
|
c29457d341 | ||
|
|
dfdee167a4 | ||
|
|
ab17ab70c5 | ||
|
|
e79b2a3ad3 | ||
|
|
53c50afca0 | ||
|
|
43862557f4 | ||
|
|
204da34651 | ||
|
|
f92fd358c8 | ||
|
|
407b8b213c | ||
|
|
9a14dba0f8 | ||
|
|
94e4b00226 | ||
|
|
19aaf067d8 | ||
|
|
9de2a69c26 | ||
|
|
059644a27d | ||
|
|
ac12db1738 | ||
|
|
bcf4485331 | ||
|
|
8493f9ca2c | ||
|
|
e89a742b24 | ||
|
|
020e02fb50 | ||
|
|
5bbd10dfbb | ||
|
|
eb102da367 | ||
|
|
2dd7d8d9ba | ||
|
|
f6690b2bee | ||
|
|
5f0bd1852d | ||
|
|
ff592f0bd3 | ||
|
|
078a713302 | ||
|
|
8fa6f80acd | ||
|
|
a0576418aa | ||
|
|
b1b892bbc8 | ||
|
|
24fbcd2859 | ||
|
|
7d421a482d | ||
|
|
14438ac5b3 | ||
|
|
93fb466075 | ||
|
|
0f5ad7e851 | ||
|
|
a61adf78b6 | ||
|
|
a1f8ee4a0e | ||
|
|
9ed0f74820 | ||
|
|
b124d8b4c4 | ||
|
|
7b40392bc5 | ||
|
|
3138c7b3c4 | ||
|
|
84e854dc39 | ||
|
|
40c592aad4 | ||
|
|
f8b0d027d7 | ||
|
|
3d90acdfa4 | ||
|
|
850850bc29 | ||
|
|
d2e02453d8 | ||
|
|
7675fec424 | ||
|
|
db6737b157 | ||
|
|
90897eccd3 | ||
|
|
2dca199f32 | ||
|
|
e63ea63cc5 | ||
|
|
cad0fa589b | ||
|
|
2072d960c8 | ||
|
|
2d631fce66 | ||
|
|
cddcce8cbf | ||
|
|
d39df3d883 | ||
|
|
90e5806813 | ||
|
|
de458b2779 | ||
|
|
ff6486ae59 | ||
|
|
20607547df | ||
|
|
661f297bd5 | ||
|
|
90ac2f56d8 | ||
|
|
3eb4b933a7 | ||
|
|
d5666fcbd0 | ||
|
|
9561e71299 | ||
|
|
55f4d705b0 | ||
|
|
246e3e2bef | ||
|
|
2ba61df85b | ||
|
|
30be1aab32 | ||
|
|
865dbbf17a | ||
|
|
fa5c799905 | ||
|
|
dc094bad11 | ||
|
|
0cc7d20b0b | ||
|
|
527e775952 | ||
|
|
43dc22c766 | ||
|
|
92dca74763 | ||
|
|
9738d0e549 | ||
|
|
3ed6695f28 | ||
|
|
bbbb10e846 | ||
|
|
6586d30961 | ||
|
|
4a86179c1b | ||
|
|
dbae78d2f9 | ||
|
|
341bdaaf44 | ||
|
|
0f36ae74ad | ||
|
|
e3abd8fb01 | ||
|
|
bba0ba7f19 | ||
|
|
3a7efd879f | ||
|
|
33e3fe12e8 | ||
|
|
f12c185b18 | ||
|
|
ca3b717395 | ||
|
|
b3a277a734 | ||
|
|
5b684e42e6 | ||
|
|
e7609f6e43 | ||
|
|
0a782b24a6 | ||
|
|
4ccc28a117 | ||
|
|
73d438de27 | ||
|
|
9f819527f5 | ||
|
|
d584f82516 | ||
|
|
bf5cda35e4 | ||
|
|
2ee8dd1e37 | ||
|
|
cea0ffd382 | ||
|
|
2e7e4299e7 | ||
|
|
a515ab8bdf | ||
|
|
dd9e41f532 | ||
|
|
9a01efe126 | ||
|
|
29fa7f6e7b | ||
|
|
de5fb29e04 | ||
|
|
119f0e464f | ||
|
|
b173ae4dc3 | ||
|
|
e94afc8a39 | ||
|
|
8946523053 | ||
|
|
a8217cbe40 | ||
|
|
a1c972c4c6 | ||
|
|
18401c4fc3 | ||
|
|
f86e2fd957 | ||
|
|
5c7fa18e4d | ||
|
|
1db58cb862 | ||
|
|
7dacfb64aa | ||
|
|
f6dc3cc324 | ||
|
|
f62cd7649b | ||
|
|
fd581083ec | ||
|
|
7d330e5804 | ||
|
|
e72a8f7c03 | ||
|
|
94cdb89f5c | ||
|
|
059e9a6844 | ||
|
|
dc412bd1b7 | ||
|
|
9d53ee1094 | ||
|
|
a8977eebcc | ||
|
|
060bd0261f | ||
|
|
989404d4f0 | ||
|
|
da23cbf28d | ||
|
|
43aba315e8 | ||
|
|
7ee51141d6 | ||
|
|
b215bc6a39 | ||
|
|
ab32251643 | ||
|
|
c37701ef1e | ||
|
|
7c97129dcf | ||
|
|
e510f1de50 | ||
|
|
0394e4dff7 | ||
|
|
419cfc1f39 | ||
|
|
6b29ad054b | ||
|
|
1456639ba9 | ||
|
|
4b9586ad8d | ||
|
|
1db51b7cfb | ||
|
|
5742c8eb23 | ||
|
|
71778ea3d8 | ||
|
|
dbbf70cd35 | ||
|
|
f64293c92d | ||
|
|
fe49c460eb | ||
|
|
689d9af022 | ||
|
|
9a0c6b2ed0 | ||
|
|
046f1019f5 | ||
|
|
543450d859 | ||
|
|
eafc717dd8 | ||
|
|
7b64fa487b | ||
|
|
d890ee248c | ||
|
|
d3d84db23d | ||
|
|
db9114986f | ||
|
|
815150de4e | ||
|
|
1d797ebe80 | ||
|
|
e65f8e490e | ||
|
|
e91a82c775 | ||
|
|
ef751a2e11 | ||
|
|
9ce48be89b | ||
|
|
a0b092c544 | ||
|
|
dce0cfafae | ||
|
|
79dd082e00 | ||
|
|
9cc764188f | ||
|
|
63874041b4 | ||
|
|
bfffb5114c | ||
|
|
f8de4ffc2d | ||
|
|
d9b1dfef76 | ||
|
|
a56e760d36 | ||
|
|
69b1e15d4c | ||
|
|
91ce89acef | ||
|
|
dcb02999b4 | ||
|
|
6782adc545 | ||
|
|
10d42421d0 | ||
|
|
9df831ea6f | ||
|
|
82c4243049 | ||
|
|
a34135a946 | ||
|
|
e8300a0288 | ||
|
|
681227e5fa | ||
|
|
3c46d4eb96 | ||
|
|
37c43d085f | ||
|
|
6388f1ba1b | ||
|
|
9a0504714c | ||
|
|
03cc059bf7 | ||
|
|
1b8f1ec467 | ||
|
|
d6b29340c6 | ||
|
|
904a852f1b | ||
|
|
ae252e0649 | ||
|
|
c02028f6f8 | ||
|
|
2905c2920c | ||
|
|
ce66959034 | ||
|
|
73d0c5e92d | ||
|
|
5dcf2ffc76 | ||
|
|
d21d7afe9a | ||
|
|
1e0676562d | ||
|
|
ee2361d012 | ||
|
|
66c4769d78 | ||
|
|
0585181b28 | ||
|
|
eb2208ce1e | ||
|
|
1968c3e1fc | ||
|
|
2b1a2340ff | ||
|
|
0703a6885f | ||
|
|
76c197cd7b | ||
|
|
0e194d7574 | ||
|
|
488e3a1121 | ||
|
|
7cb02e3cb3 | ||
|
|
9586e7f87e | ||
|
|
426e452ab5 | ||
|
|
7e7d83564f | ||
|
|
e171e802c1 | ||
|
|
dfb713eb79 | ||
|
|
b28001def5 | ||
|
|
c67194d5e6 | ||
|
|
5ce68f2d8b | ||
|
|
20b17cbdbe | ||
|
|
61697ea941 | ||
|
|
edf5c132ce | ||
|
|
dca2d7db48 | ||
|
|
d885390cab | ||
|
|
8e54b0e742 | ||
|
|
2401aa7ab5 | ||
|
|
6c0ca4ff08 | ||
|
|
366de65e8b | ||
|
|
efe493d179 | ||
|
|
a4374b61d7 | ||
|
|
3b5fa24950 | ||
|
|
7411807922 | ||
|
|
efcdb5a300 | ||
|
|
0c06d78551 | ||
|
|
573b4c9377 | ||
|
|
f9bbe4b0d6 | ||
|
|
f192e1cf27 | ||
|
|
07383f7c4c | ||
|
|
4498547824 | ||
|
|
bd7da33e77 | ||
|
|
7dc6573513 | ||
|
|
59c95f2ccf | ||
|
|
ce2841b05e | ||
|
|
bdc44a4395 | ||
|
|
f5c119e27c | ||
|
|
785c8ecdd8 | ||
|
|
58275457de | ||
|
|
fb7d3115ce | ||
|
|
c96d9c2bf7 | ||
|
|
0dff0c488b | ||
|
|
a740bc262e | ||
|
|
8ab986f2e9 | ||
|
|
1bbe8cd17c | ||
|
|
2169e15742 | ||
|
|
fa590b5eb1 | ||
|
|
196ab76e30 | ||
|
|
8329a0f653 | ||
|
|
8b1c9e0b7d | ||
|
|
b9d18fd38c | ||
|
|
30656d38c4 | ||
|
|
b9e8528029 | ||
|
|
cb082f1e31 | ||
|
|
f270e4d9cd | ||
|
|
ba5b48cb2b | ||
|
|
407616c515 | ||
|
|
29ae8f5446 | ||
|
|
2cc8166583 | ||
|
|
85bd53b0c6 | ||
|
|
7d04f015f4 | ||
|
|
51fd3f7647 | ||
|
|
ba362ede36 | ||
|
|
193336f5f3 | ||
|
|
5cf63692a8 | ||
|
|
4612dbbc59 | ||
|
|
84a82990fa | ||
|
|
04af457676 | ||
|
|
708c43edbf | ||
|
|
93ee8f7c7b | ||
|
|
ebe7d72789 | ||
|
|
6fa5e16d5f | ||
|
|
78f652dd43 | ||
|
|
10724a5161 | ||
|
|
6c40e8214d | ||
|
|
a73bd2fa3d | ||
|
|
9ee851dabc | ||
|
|
d9fba35f64 | ||
|
|
60c62c4713 | ||
|
|
832db1d89d | ||
|
|
91d34b404d | ||
|
|
a744fea295 | ||
|
|
17951d9c7f | ||
|
|
ff4b5fad66 | ||
|
|
923bc0a567 | ||
|
|
13b729134a | ||
|
|
517d927391 | ||
|
|
fa9669894d | ||
|
|
27ccd4859b | ||
|
|
850409ec62 | ||
|
|
800782a3b4 | ||
|
|
59a34ae91e | ||
|
|
5894806df5 | ||
|
|
56f225be68 | ||
|
|
c0fe6186e9 | ||
|
|
0e01478dbf | ||
|
|
668fb1e202 | ||
|
|
ceb2002374 | ||
|
|
3975b3bf6d | ||
|
|
1b8d38c417 | ||
|
|
60a94f6022 | ||
|
|
824e0f18f2 | ||
|
|
08e8e414fc | ||
|
|
0327cd73f1 | ||
|
|
46b65e835c | ||
|
|
0e47e160d6 | ||
|
|
26068c3033 | ||
|
|
401b0db85f | ||
|
|
0f06c93efc | ||
|
|
d614402a07 | ||
|
|
81f2969375 | ||
|
|
ced97449f4 | ||
|
|
399d03239d | ||
|
|
5e85516728 | ||
|
|
ca2b7887f0 | ||
|
|
3c3662dc0b | ||
|
|
74566f1f4e | ||
|
|
dddd09efe7 | ||
|
|
f313de53c0 | ||
|
|
3ae582ef97 | ||
|
|
23cab1ef8e | ||
|
|
25380e0a5c | ||
|
|
e812f41ae7 | ||
|
|
6fe794baa9 | ||
|
|
8733c63c58 | ||
|
|
54712f0c66 | ||
|
|
4f82662da8 | ||
|
|
056d50b6ba | ||
|
|
3831f4b5e2 | ||
|
|
858b8731f8 | ||
|
|
142fd19dec | ||
|
|
9f89b3528b | ||
|
|
88446acb39 | ||
|
|
d25a2774d3 | ||
|
|
07d2b0aaf3 | ||
|
|
d68ee9c507 | ||
|
|
47b9b226e1 | ||
|
|
db1479f6b9 | ||
|
|
b42c2ad489 | ||
|
|
ea7b290850 | ||
|
|
d8415d85ef | ||
|
|
4e85678560 | ||
|
|
5e335b97fa | ||
|
|
fd5a969434 | ||
|
|
b58b8e2051 | ||
|
|
38eda65143 | ||
|
|
ddf8ce3ac5 | ||
|
|
8de0472048 | ||
|
|
75931881f7 | ||
|
|
6d24ddc8e6 | ||
|
|
54f5af72f2 | ||
|
|
4d3cdaa26f | ||
|
|
e29cb17712 | ||
|
|
eb22d38770 | ||
|
|
244d98d81f | ||
|
|
e2c57c0973 | ||
|
|
6ec968cd85 | ||
|
|
0a4270970d | ||
|
|
50f978bc5c | ||
|
|
2543dd0bae | ||
|
|
2d9cdcaef6 | ||
|
|
ed612574bc | ||
|
|
20b28e3d06 | ||
|
|
bfd667de00 | ||
|
|
a551612b89 | ||
|
|
7e4b909278 | ||
|
|
8448148f97 | ||
|
|
dc3efbc739 | ||
|
|
2fd5ed045d | ||
|
|
5c63c5fe1c | ||
|
|
8f1ecbb12b | ||
|
|
8247399af8 | ||
|
|
a89106211f | ||
|
|
e1202c6839 | ||
|
|
939e461c08 | ||
|
|
653329be09 | ||
|
|
a2223fc6f9 | ||
|
|
2f02b03d6a | ||
|
|
d851a4fc8d | ||
|
|
c54d24a362 | ||
|
|
c2797969d4 | ||
|
|
f34dc83907 | ||
|
|
85ad236693 | ||
|
|
97cd2130b4 | ||
|
|
3bf91bdde6 | ||
|
|
8304196533 | ||
|
|
d1fbe5c26d | ||
|
|
b43a313793 | ||
|
|
20b89b98ed | ||
|
|
6be0b30d04 | ||
|
|
8f4909ad9b | ||
|
|
be778e451e | ||
|
|
5b1fac6734 | ||
|
|
d5b96c0051 | ||
|
|
eaa5078521 | ||
|
|
1ac0eb83cf | ||
|
|
f1907012f7 | ||
|
|
dc7f692f38 | ||
|
|
8dd5faf9c9 | ||
|
|
80963222d1 | ||
|
|
2a3456d0e2 | ||
|
|
01de70351b | ||
|
|
d002a9311c | ||
|
|
1173dca6c5 | ||
|
|
a9a9124547 | ||
|
|
aa616e557a | ||
|
|
69c0aba875 | ||
|
|
7fb93a4f1d | ||
|
|
390b041cb2 | ||
|
|
0dfbfffe76 | ||
|
|
ae5bb38c92 | ||
|
|
ccb5e6d6a5 | ||
|
|
e97b814089 | ||
|
|
6cb14c2568 | ||
|
|
b142ff53b5 | ||
|
|
6326b1c185 | ||
|
|
20b6d2c27b | ||
|
|
bacacdcb45 | ||
|
|
101521833d | ||
|
|
ff11d7156d | ||
|
|
a797a595c8 | ||
|
|
874757a79a | ||
|
|
08d7287099 | ||
|
|
487e717924 | ||
|
|
730d9ebb70 | ||
|
|
4793cf77da | ||
|
|
2c3069b5a2 | ||
|
|
176c5f1871 | ||
|
|
38e47bbc5c | ||
|
|
f7a0f528d2 | ||
|
|
6482c8e05a | ||
|
|
881905cf21 | ||
|
|
e531f7300e | ||
|
|
c78411b7ee | ||
|
|
017deaaad5 | ||
|
|
433508b70b | ||
|
|
539c66779f | ||
|
|
83dfd3c739 | ||
|
|
3c17326928 | ||
|
|
ac5e585387 | ||
|
|
499f90b7a2 | ||
|
|
b0b16feb1f | ||
|
|
7d13720dad | ||
|
|
82dcccb5b3 | ||
|
|
a3efd3dfe6 | ||
|
|
c870edd9f2 | ||
|
|
5de048a81a | ||
|
|
00dcf5c2c6 | ||
|
|
e89c39d2e1 | ||
|
|
ccd3e87403 | ||
|
|
6e2f668e72 | ||
|
|
4896e47b41 | ||
|
|
0b72fee8a3 | ||
|
|
56e5bfa234 | ||
|
|
d9deb6149c | ||
|
|
3c9bf4f9c8 | ||
|
|
e70f89b67c | ||
|
|
4521cc88fa | ||
|
|
5cd3a38fc1 | ||
|
|
4067aca8fc | ||
|
|
7fdc63c8a5 | ||
|
|
c29cdcb757 | ||
|
|
f4443ea97b | ||
|
|
a83e193133 | ||
|
|
b985642e5e | ||
|
|
d49e20cfa4 | ||
|
|
af34fda5aa | ||
|
|
7f7d2bf1dd | ||
|
|
8dbbfd5c07 | ||
|
|
02ae4d8460 | ||
|
|
e34adad368 | ||
|
|
55ec69ec79 | ||
|
|
a9cf83aec2 | ||
|
|
35fc6ee211 | ||
|
|
e61c2f1554 | ||
|
|
e6af43b060 | ||
|
|
e11fe9db8b | ||
|
|
14ba4e6894 | ||
|
|
18e2d127be | ||
|
|
b77d191754 | ||
|
|
b04f644242 | ||
|
|
570677fe15 | ||
|
|
a5848f4467 | ||
|
|
b956760d1e | ||
|
|
3118aa7b35 | ||
|
|
9b08ace35c | ||
|
|
39d1a7b3da | ||
|
|
f0216cb3da | ||
|
|
03a45a1185 | ||
|
|
f580d49a56 | ||
|
|
284829fc65 | ||
|
|
29e5316439 | ||
|
|
eedd169f15 | ||
|
|
d0227a7f99 | ||
|
|
8f6ba8553a | ||
|
|
0c9d134429 | ||
|
|
e1aa757a3d | ||
|
|
69d3db30cd | ||
|
|
3906cf9cc0 | ||
|
|
779680195d | ||
|
|
25d4e0e524 | ||
|
|
06b7f2d677 | ||
|
|
fdf58e82a3 | ||
|
|
025b6db6ef | ||
|
|
c5a62a46a8 | ||
|
|
f99bc762a9 | ||
|
|
2b0fdec1d0 | ||
|
|
cfaf795bff | ||
|
|
247ed0e597 | ||
|
|
4a0f58862f | ||
|
|
fe8ab69652 | ||
|
|
82e4dd2cfd | ||
|
|
08c6c7f725 | ||
|
|
9388a7c825 | ||
|
|
55c9783414 | ||
|
|
7cebee11a5 | ||
|
|
f927cb41e0 | ||
|
|
f53c7f5f78 | ||
|
|
8f3aec9ec1 | ||
|
|
1b7bdd2e57 | ||
|
|
4e04d7e28f | ||
|
|
99de0acc8f | ||
|
|
0bba96c1b7 | ||
|
|
84e473c5cb | ||
|
|
f91faf28d8 | ||
|
|
52d3318a9c | ||
|
|
cec608b8d0 | ||
|
|
1c559ff538 | ||
|
|
23b067ee95 | ||
|
|
009af245dc | ||
|
|
02f7ff8979 | ||
|
|
053726519c | ||
|
|
e031ac8176 | ||
|
|
ef96037555 | ||
|
|
3c1edcef4e | ||
|
|
6481842482 | ||
|
|
048737d9dd | ||
|
|
6b040f9bb8 | ||
|
|
9a57f1aca5 | ||
|
|
3f99525715 | ||
|
|
0b5067b6e8 | ||
|
|
124a9cec6c | ||
|
|
2c21bd0ffb | ||
|
|
e8cf3a1828 | ||
|
|
faa322f4a7 | ||
|
|
cb1debc8f4 | ||
|
|
0264571cce | ||
|
|
35569a69b7 | ||
|
|
9a9f5ec268 | ||
|
|
2a69451834 | ||
|
|
5f560c2266 | ||
|
|
10be0a93a4 | ||
|
|
907825f53b | ||
|
|
755d2476e0 | ||
|
|
101727b5da | ||
|
|
2de0a4d164 | ||
|
|
32d276cb7e | ||
|
|
95cf25d531 | ||
|
|
7653f50f9d | ||
|
|
599407ff14 | ||
|
|
b4d57200a6 | ||
|
|
4e15c2ddd4 | ||
|
|
1a8b0bded3 | ||
|
|
9650bacca5 | ||
|
|
ba29d1cfd9 | ||
|
|
51a0a9e70a | ||
|
|
70a82fbc45 | ||
|
|
144f8944e1 | ||
|
|
8d3c4ff443 | ||
|
|
31113133fc | ||
|
|
2bf72bca6e | ||
|
|
1b01336514 | ||
|
|
d52deee44e | ||
|
|
d4c25caeef | ||
|
|
614e98d32a | ||
|
|
00360e55d2 | ||
|
|
8597956aeb | ||
|
|
8c898b79b8 | ||
|
|
544cfc5044 | ||
|
|
e3f0de8f84 | ||
|
|
b160281cfe | ||
|
|
321a44d0e9 | ||
|
|
3bf52a1237 | ||
|
|
466297712d | ||
|
|
43f60a5927 | ||
|
|
2d47b99618 | ||
|
|
cb66655c18 | ||
|
|
981dcade2b | ||
|
|
fc15e64c54 | ||
|
|
fdcb9bd7a4 | ||
|
|
e6f4661077 | ||
|
|
912f28c06a | ||
|
|
561d0fec79 | ||
|
|
eff5e56219 | ||
|
|
e594c6f11e | ||
|
|
c28e8a700d | ||
|
|
617ff419b9 | ||
|
|
d9b5d19e8f | ||
|
|
2d26abac38 | ||
|
|
2d31093d2d | ||
|
|
7f30db66b6 | ||
|
|
a05c75f838 | ||
|
|
efae93345a | ||
|
|
e236a107c9 | ||
|
|
b033ab0bba | ||
|
|
e80893e4a0 | ||
|
|
f9aa7ece1c | ||
|
|
1000b73ecb | ||
|
|
4105d54582 | ||
|
|
270bd3cb73 | ||
|
|
b581d722c3 | ||
|
|
bc84022d52 | ||
|
|
508b1c6c49 | ||
|
|
d66ce972f0 | ||
|
|
adc7ea417e | ||
|
|
3a5bffdb0c | ||
|
|
2c27c2754f | ||
|
|
9877a8d15f | ||
|
|
50d9003ea8 | ||
|
|
172600a94d | ||
|
|
1b8bc63b07 | ||
|
|
bb12706151 | ||
|
|
18f241f036 | ||
|
|
65aa1b8d89 | ||
|
|
b91517812a | ||
|
|
66c9b2ae25 | ||
|
|
9739dffd71 | ||
|
|
8cd5948375 | ||
|
|
22d9c09403 | ||
|
|
336b1f309a | ||
|
|
86fd37b80b | ||
|
|
54fbb78bea | ||
|
|
b6a2d9ed73 | ||
|
|
118d2333ec | ||
|
|
3ee4535ebd | ||
|
|
8fb779b7ec | ||
|
|
295bfbe8a3 | ||
|
|
ccc2629e47 | ||
|
|
ac3bec4332 | ||
|
|
03551d6968 | ||
|
|
f6c9bdb5e2 | ||
|
|
8cc0744698 | ||
|
|
5f7bcb4d69 | ||
|
|
88538a500f | ||
|
|
871065bdca | ||
|
|
26c2845d4f | ||
|
|
bee2be36cf | ||
|
|
6897f36e03 | ||
|
|
117ff56bbc | ||
|
|
d3ff88f934 | ||
|
|
93b5eb0b1e | ||
|
|
712a907f34 | ||
|
|
e13ae86a05 | ||
|
|
41e048d382 | ||
|
|
87d236828f | ||
|
|
8d73b8e132 | ||
|
|
a1400a0318 | ||
|
|
65e88caed4 | ||
|
|
14413dcdd7 | ||
|
|
5c90cf5a2c | ||
|
|
d2473fe3a6 | ||
|
|
64725a4c41 | ||
|
|
4330d95866 | ||
|
|
6897d1d31a | ||
|
|
63bb900889 | ||
|
|
c1ca1f0a99 | ||
|
|
98762a6c77 | ||
|
|
fd98f9b7f9 | ||
|
|
946a1e9cb4 | ||
|
|
244b52a8b3 | ||
|
|
e82103ba94 | ||
|
|
cf792b7ed5 | ||
|
|
31b76892cd | ||
|
|
ba942bff3d | ||
|
|
c5a351f6ca | ||
|
|
27e9a56d04 | ||
|
|
8b00ab6bf5 | ||
|
|
86cbc8a613 | ||
|
|
69c17e9b4c | ||
|
|
01d91402fc | ||
|
|
281a8c7e66 | ||
|
|
ef758638af | ||
|
|
0b2331b0e6 | ||
|
|
8d114e42f1 | ||
|
|
89fef80d05 | ||
|
|
c9a0058042 | ||
|
|
740884847f | ||
|
|
5a68bc4b79 | ||
|
|
f3006fd8e2 | ||
|
|
0b616164c4 | ||
|
|
3cfaa8ad99 | ||
|
|
187256cdb7 | ||
|
|
adc2d95b1f | ||
|
|
2973214976 | ||
|
|
f8418f689f | ||
|
|
8d03b23eb4 | ||
|
|
d1dd1c7530 | ||
|
|
9aeed6ac31 | ||
|
|
fcc8a8f9ce | ||
|
|
67e5ae9ee0 | ||
|
|
2b49f70ae3 | ||
|
|
7813483a08 | ||
|
|
045b303deb | ||
|
|
57af402bcf | ||
|
|
e4b7c105b1 | ||
|
|
3b5d273409 | ||
|
|
07c29999c6 | ||
|
|
9dc7632e68 | ||
|
|
442ffdcb37 | ||
|
|
f02ba82471 | ||
|
|
f628a4aa65 | ||
|
|
20b80bced6 | ||
|
|
801193edde | ||
|
|
8eaf76734e | ||
|
|
9a1dcd3cda | ||
|
|
553cec6d60 | ||
|
|
c333fec6c4 | ||
|
|
7e9efd9c30 | ||
|
|
8e7f055b59 | ||
|
|
ece779c88e | ||
|
|
c1da970c47 | ||
|
|
3505047c9b | ||
|
|
1338af9720 | ||
|
|
e0dd4d3b4f | ||
|
|
1458184500 | ||
|
|
52fe47c3ee | ||
|
|
1a81f24c45 | ||
|
|
63210a42b0 | ||
|
|
09900a3457 | ||
|
|
7071be93f7 | ||
|
|
96a7d7d0d2 | ||
|
|
2974e33751 | ||
|
|
854205f86f | ||
|
|
74e4f2aa4a | ||
|
|
eadb5aebb7 | ||
|
|
ae1de32654 | ||
|
|
b3d70f1f0d | ||
|
|
b7651abf4a | ||
|
|
7005a2fbe4 | ||
|
|
e9dd24e6c8 | ||
|
|
ed034bdec7 | ||
|
|
bbd8c0c752 | ||
|
|
1bb807e302 | ||
|
|
114185ae1e | ||
|
|
b91791c747 | ||
|
|
e51fdd8c56 | ||
|
|
8c695fd4d9 | ||
|
|
7823c28540 | ||
|
|
7aed102941 | ||
|
|
15e8ac7640 | ||
|
|
5e5d78e595 | ||
|
|
5bfb8aa8ac | ||
|
|
9345f7bb23 | ||
|
|
261da41e85 | ||
|
|
ee8c7ba84e | ||
|
|
64e3718122 | ||
|
|
c0e715717b | ||
|
|
4a4e38fdd0 | ||
|
|
05bc65aba9 | ||
|
|
ac27e07225 | ||
|
|
e9d8e9ba78 | ||
|
|
53a521ecd2 | ||
|
|
ff12e5b088 | ||
|
|
2fada1da8d | ||
|
|
c003ddeeac | ||
|
|
df7f893302 | ||
|
|
21c41550f8 | ||
|
|
e38a73b590 | ||
|
|
8e6755482a | ||
|
|
c96b918fad | ||
|
|
ecb6a10c7f | ||
|
|
790a461622 | ||
|
|
ec94798cc2 | ||
|
|
b94b1e3df1 | ||
|
|
ef75e00184 | ||
|
|
5c03d27538 | ||
|
|
ccb1aa9ef0 | ||
|
|
54e2951e4b | ||
|
|
e8c6f29577 | ||
|
|
e0880f1932 | ||
|
|
465c450b6b | ||
|
|
0d4088ff7a | ||
|
|
7e3b9ccc2d | ||
|
|
d96d1f2ee9 | ||
|
|
ed76e62d56 | ||
|
|
43e4d8a914 | ||
|
|
714a77f451 | ||
|
|
3f8651f586 | ||
|
|
a25592ebfd | ||
|
|
4a131b63d8 | ||
|
|
2e8ee84485 | ||
|
|
fb1c545a98 | ||
|
|
0a03261dbb | ||
|
|
1c3fc52b6a | ||
|
|
b091b892e9 | ||
|
|
74e84dc381 | ||
|
|
1d4d18566c | ||
|
|
4c083a127f | ||
|
|
1df11a1f81 | ||
|
|
41698bfddd | ||
|
|
39d143cebc | ||
|
|
4576bb28ac | ||
|
|
998efa11e3 | ||
|
|
66870cdd05 | ||
|
|
42ead5b9eb | ||
|
|
852e12c903 | ||
|
|
058aabb666 | ||
|
|
34ee393224 | ||
|
|
816660f80d | ||
|
|
1e1a16a4f4 | ||
|
|
bf2eed7cf5 | ||
|
|
d13201fd47 | ||
|
|
5debe17b08 | ||
|
|
aa34d92668 | ||
|
|
279ff2c765 | ||
|
|
1ebb4aa091 | ||
|
|
176705e703 | ||
|
|
82afad3b67 | ||
|
|
5d43733add | ||
|
|
e07b34f9cb | ||
|
|
a1203b2c4a | ||
|
|
f0ce6277ac | ||
|
|
95bd867cee | ||
|
|
ac63687c33 | ||
|
|
924e063168 | ||
|
|
8a52feaf6d | ||
|
|
773358e538 | ||
|
|
44fc9be4a2 | ||
|
|
697be27d4f | ||
|
|
fb23e5a663 | ||
|
|
12631ffee6 | ||
|
|
cbce073227 | ||
|
|
4103afc6cf | ||
|
|
c2ddc8cd87 | ||
|
|
52a5f3b0a4 | ||
|
|
91d80f49f4 | ||
|
|
e0b1bc7cd0 | ||
|
|
438d4f6e38 | ||
|
|
473fa088ce | ||
|
|
e52b9d62f7 | ||
|
|
c7fb979fe2 | ||
|
|
198e7adc47 | ||
|
|
4cf79222f0 | ||
|
|
94a9597963 | ||
|
|
469649c0c4 | ||
|
|
bd08c5ca9d | ||
|
|
1296b24960 | ||
|
|
87b8ac38f2 | ||
|
|
51dca51014 | ||
|
|
07c448ba16 | ||
|
|
0033edbeda | ||
|
|
d7e701d7a5 | ||
|
|
63bd305250 | ||
|
|
dc789addb9 | ||
|
|
6b1c0a0eda | ||
|
|
a3f36bd7a4 | ||
|
|
1258e24ee0 | ||
|
|
f9832a51cf | ||
|
|
018a3d8c91 | ||
|
|
ec1a4579e1 | ||
|
|
bfde06f0d6 | ||
|
|
b8c207ac2a | ||
|
|
9b2e54c001 | ||
|
|
62585848bd | ||
|
|
d47acc16a9 | ||
|
|
a34e97af52 | ||
|
|
319b587c1b | ||
|
|
07f0f06767 | ||
|
|
ac3513ac8a | ||
|
|
5760520ca2 | ||
|
|
236f456097 | ||
|
|
2965e0fc28 | ||
|
|
0ccdbde2f7 | ||
|
|
b2659d6c38 | ||
|
|
52b300650c | ||
|
|
0b05bde1e3 | ||
|
|
b72b528cb3 | ||
|
|
ab98c1df7d | ||
|
|
bc63cdd52a | ||
|
|
ada5aef26f | ||
|
|
df1050668f | ||
|
|
4dc13f9417 | ||
|
|
d71da723a0 | ||
|
|
c5ce102f4d | ||
|
|
3d291444bc | ||
|
|
270e25083a | ||
|
|
b6f55309ae | ||
|
|
4afeaeb170 | ||
|
|
932dde8177 | ||
|
|
41b4ae9aae | ||
|
|
9d66d36a78 | ||
|
|
a4133a07a8 | ||
|
|
f73d139aa7 | ||
|
|
d6ca320c61 | ||
|
|
bbf645bc6b | ||
|
|
ea547dfd4d | ||
|
|
d990f245d5 | ||
|
|
bd0bad4254 | ||
|
|
a0c2c41203 | ||
|
|
74606f6b04 | ||
|
|
cca8f84f61 | ||
|
|
b3aaf2e0e4 | ||
|
|
5bf954c503 | ||
|
|
c66a8c20d2 | ||
|
|
d664f8abcd | ||
|
|
403b1f3c39 | ||
|
|
38697f0835 | ||
|
|
0b5beddd51 | ||
|
|
a6c3a642f8 | ||
|
|
5e8d761a41 | ||
|
|
b4fd663329 | ||
|
|
124c1632af | ||
|
|
3867287c0f | ||
|
|
c1c0424af9 | ||
|
|
cd9d70baee | ||
|
|
13aec8f621 | ||
|
|
7b5e7afd6d | ||
|
|
a586be2c41 | ||
|
|
4630623d35 | ||
|
|
71a2e68551 | ||
|
|
ef71e037f2 | ||
|
|
df30218f55 | ||
|
|
e8a84ad571 | ||
|
|
43dfbed157 | ||
|
|
8b8605b773 | ||
|
|
9ef27b2699 | ||
|
|
faad9e0d2c | ||
|
|
6a9ba00aca | ||
|
|
5329138d0f | ||
|
|
ed0109af94 | ||
|
|
92556d5cb2 | ||
|
|
1a3308017a | ||
|
|
64e8dd35c2 | ||
|
|
bf5d8b5472 | ||
|
|
f8beba2815 | ||
|
|
8c325d8264 | ||
|
|
9aec30e0dc | ||
|
|
5049b7885a | ||
|
|
3eead9da4e | ||
|
|
9a206ff338 | ||
|
|
4f487f3009 | ||
|
|
d7a55e4c7d | ||
|
|
cc654b56a1 | ||
|
|
66ac3ee63c | ||
|
|
e421a32475 | ||
|
|
1acb0e9e95 | ||
|
|
ab2d284dee | ||
|
|
f72c5aa59c | ||
|
|
910dc4fb26 | ||
|
|
4bf54c721d | ||
|
|
b482964373 | ||
|
|
01017efab6 | ||
|
|
49558ca39d | ||
|
|
758dbb689a | ||
|
|
536f9f4409 | ||
|
|
c6cab341ed | ||
|
|
792fdad4be | ||
|
|
bd097888a3 | ||
|
|
eae90194cb | ||
|
|
0a717bb9be | ||
|
|
dc71046018 | ||
|
|
881572cba5 | ||
|
|
4a41c57e93 | ||
|
|
084f45393e | ||
|
|
90a1a5f058 | ||
|
|
e0486aff29 | ||
|
|
ce165f4234 | ||
|
|
4d73f954f8 | ||
|
|
445625ba1e | ||
|
|
e76e610b97 | ||
|
|
8c50ddef2f | ||
|
|
d11c608bd0 | ||
|
|
9aac61e350 | ||
|
|
6436d6fa2a | ||
|
|
8f68266585 | ||
|
|
38376b3978 | ||
|
|
7d5b0be5ca | ||
|
|
98bc1fb01a | ||
|
|
aa11704caa | ||
|
|
bc701fecb3 | ||
|
|
e8462e8291 | ||
|
|
d74c6a2e21 | ||
|
|
bddd94aec0 | ||
|
|
665d07cd56 | ||
|
|
7375ad8a5d | ||
|
|
581461f0e3 | ||
|
|
3dc3da5932 | ||
|
|
0464601dc5 | ||
|
|
3f31bf85fb | ||
|
|
7c4429da46 | ||
|
|
b73efba916 | ||
|
|
58cbcee5ba | ||
|
|
ab8b71b152 | ||
|
|
ca71a997dd | ||
|
|
ce6ddd2f91 | ||
|
|
c938f8e731 | ||
|
|
7df7703c50 | ||
|
|
33bc3557e5 | ||
|
|
cf2bc158a0 | ||
|
|
e8d0fe07f4 | ||
|
|
a4fa7cb5ee | ||
|
|
6ea52c93fe | ||
|
|
b28887e91c | ||
|
|
263b906aec | ||
|
|
acef14aadc | ||
|
|
055ebac496 | ||
|
|
285370fe35 | ||
|
|
23af3953e6 | ||
|
|
83665d0992 | ||
|
|
c6dcb9db2b | ||
|
|
b3205d43db | ||
|
|
4feefd404b | ||
|
|
b202642793 | ||
|
|
ea3e91483a | ||
|
|
4ae6d9030a | ||
|
|
c97353cb3d | ||
|
|
71b2f2fa26 | ||
|
|
3c8915b002 | ||
|
|
a54a9beb87 | ||
|
|
0ad2dcb234 | ||
|
|
2b34c68057 | ||
|
|
f7a739a023 | ||
|
|
3d38b029a1 | ||
|
|
9ffad0437a | ||
|
|
844c083ee4 | ||
|
|
813b20a55c | ||
|
|
a8277f5e13 | ||
|
|
922c7e148c | ||
|
|
6f421e688a | ||
|
|
e5db8ff8b3 | ||
|
|
75d6ea6537 | ||
|
|
4a8d4346be | ||
|
|
1522baa97d | ||
|
|
a45f068a39 | ||
|
|
ec266d6d9b | ||
|
|
8c64bd64cf | ||
|
|
4dadeaa083 | ||
|
|
ade17c5eaf | ||
|
|
e44686b075 | ||
|
|
55d97404f2 | ||
|
|
e60d5173f4 | ||
|
|
30d4bb923b | ||
|
|
659a2e5a55 | ||
|
|
0b128cc4ab | ||
|
|
a4c1fea08b | ||
|
|
8a50cb9ea3 | ||
|
|
62a6839f84 | ||
|
|
023af864b6 | ||
|
|
d0e167d93c | ||
|
|
33c6e01d34 | ||
|
|
4312791cf6 | ||
|
|
ad1c2b6f0b | ||
|
|
78525a830b | ||
|
|
b02f8e5dc7 | ||
|
|
28998e97ab | ||
|
|
8c3fefd3ff | ||
|
|
819301c7d8 | ||
|
|
76df4d2412 | ||
|
|
6e95a36fb2 | ||
|
|
a6e81e5d09 | ||
|
|
b7e48ac2cc | ||
|
|
35dc1bf028 | ||
|
|
816ff7a8eb | ||
|
|
2b41c97355 | ||
|
|
131def83b9 | ||
|
|
e40211ad49 | ||
|
|
30f8e8b16c | ||
|
|
5a9547e79b | ||
|
|
0d375ecd93 | ||
|
|
970f684345 | ||
|
|
992372913e | ||
|
|
28f328b0bc | ||
|
|
2196551126 | ||
|
|
84653d7aae | ||
|
|
7cb11e5191 | ||
|
|
916b026b12 | ||
|
|
c6406e9f16 | ||
|
|
2b14e25935 | ||
|
|
2f8fd5c359 | ||
|
|
f342fe3351 | ||
|
|
a60f5d1660 | ||
|
|
e6c4f6c703 | ||
|
|
51bf8a5286 | ||
|
|
7df721a863 | ||
|
|
ee7a3e934e | ||
|
|
04254fcb00 | ||
|
|
6911906c4f | ||
|
|
47cf2040f6 | ||
|
|
c47da9acdd | ||
|
|
a11d5a0fa5 | ||
|
|
3c2bb6dfe2 | ||
|
|
d3be1a5bec | ||
|
|
61792e7875 | ||
|
|
c470e0cdcb | ||
|
|
342b84e7ad | ||
|
|
d98a0be80a | ||
|
|
a43907fe14 | ||
|
|
98cc18b12f | ||
|
|
226b0bee68 | ||
|
|
6189e0cb74 | ||
|
|
23470629bd | ||
|
|
d073fb9417 | ||
|
|
d623119eac | ||
|
|
5844de9b3c | ||
|
|
a1a7390d80 | ||
|
|
dd2bc70f47 | ||
|
|
a6acf6527c | ||
|
|
5310a0d6b7 | ||
|
|
a2b931571e | ||
|
|
da9543e426 | ||
|
|
2cfd036466 | ||
|
|
13b058ed1b | ||
|
|
0c8acd32d8 | ||
|
|
fc62a790cd | ||
|
|
eb0724954b | ||
|
|
0168a20e7e | ||
|
|
b394bd3eac | ||
|
|
d0b4fa9328 | ||
|
|
3b50fde76e | ||
|
|
a7ab8deac4 | ||
|
|
a2aa269851 | ||
|
|
d31e1a65a8 | ||
|
|
76cad4ae7e | ||
|
|
cda39c2a05 | ||
|
|
6cc4276288 | ||
|
|
c829915948 | ||
|
|
1dcb56d223 | ||
|
|
90a2e0a9e1 | ||
|
|
65aad0b09a | ||
|
|
b5a3ac6163 | ||
|
|
ed7ed059a2 | ||
|
|
d0a279e04e | ||
|
|
4303e09fe3 | ||
|
|
ac9b04de67 | ||
|
|
e79256e836 | ||
|
|
1dcfcc3531 | ||
|
|
11b1d29df6 | ||
|
|
ad30caa7c8 | ||
|
|
53a03b94d4 | ||
|
|
77da5c973b | ||
|
|
622f64ad29 | ||
|
|
0700e7a736 | ||
|
|
4e1e06c8f7 | ||
|
|
3bb69e356c | ||
|
|
9993366ade | ||
|
|
e5d436c295 | ||
|
|
211157c0d0 | ||
|
|
f83e097769 | ||
|
|
dcacdf65ce | ||
|
|
4b61620538 | ||
|
|
8b0a5e886e | ||
|
|
1045204fb7 | ||
|
|
7e1226bc3d | ||
|
|
b8deb244ca | ||
|
|
6a47d10d7b | ||
|
|
694ac39e89 | ||
|
|
a295f34094 | ||
|
|
6bb66646b8 | ||
|
|
6c0112262e | ||
|
|
4bae4c7491 | ||
|
|
e186356591 | ||
|
|
f3afaf9c51 | ||
|
|
6efd631fd8 | ||
|
|
0aea9ce153 | ||
|
|
4925db1b66 | ||
|
|
72baafe4f2 | ||
|
|
64b98a84f5 | ||
|
|
db3e4d9e83 | ||
|
|
338ba5afdc | ||
|
|
f2ae137947 | ||
|
|
c035636e94 | ||
|
|
f51b80739a | ||
|
|
d1eea7e7ba | ||
|
|
a225cf0f92 | ||
|
|
ae5f85352e | ||
|
|
ffc414062b | ||
|
|
da50071765 | ||
|
|
3b9ea37907 | ||
|
|
1a5710c564 | ||
|
|
9650014506 | ||
|
|
eda7fee2c6 | ||
|
|
79c9c914e2 | ||
|
|
190db26ebd | ||
|
|
857f45f9ec | ||
|
|
5c9a27c930 | ||
|
|
645d70e6ce | ||
|
|
130fc4ee18 | ||
|
|
ea32363144 | ||
|
|
077eb106e3 | ||
|
|
3070dddf96 | ||
|
|
708668df3f | ||
|
|
ea1770b1a7 | ||
|
|
1cfb937490 | ||
|
|
41e1997d73 | ||
|
|
e359042766 | ||
|
|
ae05e58a13 | ||
|
|
ad6f26f6a9 | ||
|
|
2c9b23fc6d | ||
|
|
defaebd064 | ||
|
|
1fb559aedb | ||
|
|
29292956b6 | ||
|
|
ffaf0eb8d5 | ||
|
|
3344453253 | ||
|
|
b60d0e057d | ||
|
|
9d0ffaddc5 | ||
|
|
e1ed1ae69c | ||
|
|
1bb5f10fad | ||
|
|
c0d65afde4 | ||
|
|
e37ac89b88 | ||
|
|
cbd1ecec12 | ||
|
|
536dfb5f2d | ||
|
|
6a288419bc | ||
|
|
721ce4196b | ||
|
|
3a427188dd | ||
|
|
0ca1166f0c | ||
|
|
ec2e04f401 | ||
|
|
d220b49324 | ||
|
|
ec467a81db | ||
|
|
0265bcc038 | ||
|
|
85458cfe5a | ||
|
|
802d084070 | ||
|
|
dfa409da1b | ||
|
|
c05a3c64ee | ||
|
|
17fc231db6 | ||
|
|
9c6eb6e071 | ||
|
|
9034e0787d | ||
|
|
a36b2ed13e | ||
|
|
71647e5bc9 | ||
|
|
7a8c82c226 | ||
|
|
00ef5bc0e6 | ||
|
|
b5686b44c4 | ||
|
|
431f329933 | ||
|
|
0e79da4944 | ||
|
|
915f9f00f5 | ||
|
|
a524ed4439 | ||
|
|
fbfc4c43f4 | ||
|
|
9be7b35bd7 | ||
|
|
b20733d492 | ||
|
|
f69a8af83f | ||
|
|
1145a3a513 | ||
|
|
1c4670e42f | ||
|
|
268ccc2cd5 | ||
|
|
2e342b32f2 | ||
|
|
32737ed9a3 | ||
|
|
a280772be0 | ||
|
|
f75ed58a97 | ||
|
|
f1084158e1 | ||
|
|
aae587c618 | ||
|
|
ac72402af4 | ||
|
|
f8d0281931 | ||
|
|
06e192f595 | ||
|
|
75eb551e22 | ||
|
|
64796da026 | ||
|
|
d00914a11c | ||
|
|
325e404de1 | ||
|
|
e28b683b89 | ||
|
|
82bd0325cf | ||
|
|
a88c8fba76 | ||
|
|
f561f4d67b | ||
|
|
9a02c5394d | ||
|
|
ec09738321 | ||
|
|
2258506383 | ||
|
|
f2af85597e | ||
|
|
e4e08609d9 | ||
|
|
98e16b2e28 | ||
|
|
5af6197c9d | ||
|
|
73c9b94446 | ||
|
|
0ad4af8927 | ||
|
|
ad87aa3eca | ||
|
|
3caf94b919 | ||
|
|
3cc1246bf4 | ||
|
|
d89f732faa | ||
|
|
2f6aa43d40 | ||
|
|
15c9281988 | ||
|
|
2b8f650a6c | ||
|
|
90aa0d8c61 | ||
|
|
ec5e8302d7 | ||
|
|
ce1df9a7ec | ||
|
|
421eb35e2c | ||
|
|
393b7c76bd | ||
|
|
efc4cbc07f | ||
|
|
85f710dafa | ||
|
|
8380c23bca | ||
|
|
7ec77ee40a | ||
|
|
7c0c3ee25b | ||
|
|
b92a6ab04b | ||
|
|
1b19c85611 | ||
|
|
5e1848270f | ||
|
|
e1f262bd26 | ||
|
|
547d36793a | ||
|
|
31e711bd6a | ||
|
|
f2483766cf | ||
|
|
3ea41572bc | ||
|
|
1a29e845b1 | ||
|
|
e7cdc38284 | ||
|
|
69ad66ce49 | ||
|
|
3602256bdf | ||
|
|
99a3c88280 | ||
|
|
fddfa257df | ||
|
|
af7dc5a59a | ||
|
|
31a50bb495 | ||
|
|
f76d2adb7c | ||
|
|
0017763514 | ||
|
|
4cdf98dfaf | ||
|
|
e3c833770a | ||
|
|
1b62113fd3 | ||
|
|
a9010d992e | ||
|
|
2590c7a65f | ||
|
|
ea163384c1 | ||
|
|
93a0503455 | ||
|
|
fb23bd0ac1 | ||
|
|
c32486a213 | ||
|
|
300d7f4662 | ||
|
|
edabd66c98 | ||
|
|
64ef53a3d6 | ||
|
|
cbc006c50a | ||
|
|
fa3ebfc9b5 | ||
|
|
ba637664d5 | ||
|
|
e1ec71b91b | ||
|
|
1498d27a64 | ||
|
|
ddb8921c35 | ||
|
|
5ea35caa91 | ||
|
|
8697472989 | ||
|
|
d40e66f21c | ||
|
|
709cc90bcb | ||
|
|
a9c2a59441 | ||
|
|
220d8e962e | ||
|
|
00df39d2ea | ||
|
|
10904c24a3 | ||
|
|
c06bd35bd5 | ||
|
|
6f4fd0ae2a | ||
|
|
05569ca1ee | ||
|
|
35655673fa | ||
|
|
23c17b1937 | ||
|
|
39369d6eb0 | ||
|
|
5c3a974c90 | ||
|
|
33613369cd | ||
|
|
866cdd3704 | ||
|
|
468b9d655a | ||
|
|
28c78a1339 | ||
|
|
5df48873eb | ||
|
|
b80e5c712a | ||
|
|
b54403163d | ||
|
|
f7bed9089c | ||
|
|
d325ce7a56 | ||
|
|
a3dc449cc9 | ||
|
|
c20093280c | ||
|
|
115720cc53 | ||
|
|
cd1c0cc526 | ||
|
|
8c53689e17 | ||
|
|
d3fe888238 | ||
|
|
333ba13869 | ||
|
|
d77fa6a390 | ||
|
|
19b86606ce | ||
|
|
6ae9f5468a | ||
|
|
df2f48b860 | ||
|
|
daae5ecec6 | ||
|
|
ab0d6ecc1f | ||
|
|
f4ec8352d0 | ||
|
|
fc0777b9ff | ||
|
|
55e492ee5d | ||
|
|
5530adca42 | ||
|
|
7c4e0329bb | ||
|
|
6ff0dd5327 | ||
|
|
9a5b2594f8 | ||
|
|
6f57dab6ba | ||
|
|
5154bbc070 | ||
|
|
c20029003c | ||
|
|
6a9e95b70a | ||
|
|
dfce092dbb | ||
|
|
8b59040a9a | ||
|
|
bb39f66a32 | ||
|
|
019bc27cf2 | ||
|
|
ab6de1752f | ||
|
|
5a9dc2e09a | ||
|
|
de45adf797 | ||
|
|
d308fba3eb | ||
|
|
3c5e4dd944 | ||
|
|
43a7e895a4 | ||
|
|
086ea97365 | ||
|
|
1c9b3e5d22 | ||
|
|
2db32d289f | ||
|
|
4d33823f28 | ||
|
|
73b075d0e7 | ||
|
|
6f93fb29b4 | ||
|
|
0bf80df486 | ||
|
|
b84aecc1c6 | ||
|
|
2ca008861d | ||
|
|
4362291fb7 | ||
|
|
563f818523 | ||
|
|
28b4355d7b | ||
|
|
faca4089b7 | ||
|
|
e7c8fe966f | ||
|
|
9e2447f601 | ||
|
|
3b8059281b | ||
|
|
4103a007db | ||
|
|
e59446c628 | ||
|
|
5e548465dc | ||
|
|
52cbacd306 | ||
|
|
666459c011 | ||
|
|
f6dc955d4b | ||
|
|
a71f4dc0b2 | ||
|
|
939b25efd8 | ||
|
|
729f35b6c1 | ||
|
|
97995f9b8c | ||
|
|
2997deac66 | ||
|
|
a77e12b078 | ||
|
|
f84ec51206 | ||
|
|
421b70b4f6 | ||
|
|
1911d6915c | ||
|
|
b4526df691 | ||
|
|
6e0ba80d12 | ||
|
|
48fdac42f4 | ||
|
|
cb5dca5c69 | ||
|
|
36c699970b | ||
|
|
7ebb70247c | ||
|
|
4a65c70668 | ||
|
|
4f6cddebd3 | ||
|
|
b7a0500f25 | ||
|
|
809b047523 | ||
|
|
ae2c56abce | ||
|
|
68ad165fb1 | ||
|
|
afe040d0b6 | ||
|
|
9a43c1b3ae | ||
|
|
7e58e204ed | ||
|
|
e51d39dcc3 | ||
|
|
93047c5fda | ||
|
|
a5d964a334 | ||
|
|
b5be110f0c | ||
|
|
81ea744e38 | ||
|
|
3e1f5c7632 | ||
|
|
00a2a1ef4d | ||
|
|
ba793ac44e | ||
|
|
49dd3c0659 | ||
|
|
def7ba5faf | ||
|
|
e01eab131f | ||
|
|
0636695052 | ||
|
|
1e893af9df | ||
|
|
0f5c6c0b0c | ||
|
|
f014285143 | ||
|
|
2f194a99d3 | ||
|
|
63094bee7a | ||
|
|
7d2965ee34 | ||
|
|
aabfb0ab1c | ||
|
|
bd321edd20 | ||
|
|
334be5afc7 | ||
|
|
0bc12e744e | ||
|
|
15772e20ac | ||
|
|
75d3972196 | ||
|
|
e89d6f7de3 | ||
|
|
1a5f79732a | ||
|
|
1e2077cf91 | ||
|
|
6dbdfe2fb4 | ||
|
|
e7702f97b0 | ||
|
|
e1c47289c9 | ||
|
|
2efa5f6f14 | ||
|
|
bb1ab54d13 | ||
|
|
f49bd65526 | ||
|
|
37624bec61 | ||
|
|
b4bd18c185 | ||
|
|
8bae5dd93b | ||
|
|
6e98c98c11 | ||
|
|
37e4d49d3d | ||
|
|
48f56beaab | ||
|
|
9cef0d752a | ||
|
|
e0b7061ac3 | ||
|
|
304aa4eeeb | ||
|
|
79b614e5c3 | ||
|
|
af5b20fcd5 | ||
|
|
f30e21ab4b | ||
|
|
6c04084891 | ||
|
|
70adc36089 | ||
|
|
f6b85ce384 | ||
|
|
cce114ce04 | ||
|
|
6ba0577270 | ||
|
|
ac0755c8d3 | ||
|
|
03a17a88a1 | ||
|
|
f593cf4e09 | ||
|
|
80d24dc8a1 | ||
|
|
4b3ce9e701 | ||
|
|
89c6ad5f9d | ||
|
|
99e451005c | ||
|
|
635c898016 | ||
|
|
691c0501ea | ||
|
|
7c8ddb2132 | ||
|
|
22ede8bf66 | ||
|
|
0cebf1552f | ||
|
|
e8fb4f50b3 | ||
|
|
a5ebf637bd | ||
|
|
6e4fac03ec | ||
|
|
58f4c1ee7e | ||
|
|
faec1f969c | ||
|
|
5ab42eaa1d | ||
|
|
ccab7cd9e1 | ||
|
|
24d86684ef | ||
|
|
633a1d644a | ||
|
|
59057e69a3 | ||
|
|
6ce5261dbd | ||
|
|
3b501003ee | ||
|
|
8e87805689 | ||
|
|
33e7caf965 | ||
|
|
5af448c039 | ||
|
|
542b3da250 | ||
|
|
acdb17d28c | ||
|
|
f9e90f0b97 | ||
|
|
4810ba6378 | ||
|
|
e61c8d83f5 | ||
|
|
671340e12f | ||
|
|
3c7ac3156a | ||
|
|
d9f2d17d15 | ||
|
|
7d760fe65b | ||
|
|
53f0cc6f21 | ||
|
|
0a16859c4d | ||
|
|
32795034b5 | ||
|
|
e0b474d2d4 | ||
|
|
e82ee5ac14 | ||
|
|
b4b9a4672d | ||
|
|
c64b70be2e | ||
|
|
cf95c37f39 | ||
|
|
ca855da46e | ||
|
|
92be2a787a | ||
|
|
d166f0afea | ||
|
|
ab1e21079f | ||
|
|
4956dba6ea | ||
|
|
c9701b3631 | ||
|
|
f61e5d8e14 | ||
|
|
773c3fd0d5 | ||
|
|
f0b743a7cf | ||
|
|
5453b8f051 | ||
|
|
dbe440b3ee | ||
|
|
b410285758 | ||
|
|
a591fefcbc | ||
|
|
d4fc353909 | ||
|
|
8c81064453 | ||
|
|
fd553e9940 | ||
|
|
872f411250 | ||
|
|
0cd4993fb0 | ||
|
|
582d4d19e1 | ||
|
|
9a159bd672 | ||
|
|
29a798e2a9 | ||
|
|
ee4e95b7e6 | ||
|
|
11a04ffc15 | ||
|
|
0391bf1cce | ||
|
|
43d53ec2b4 | ||
|
|
b44bf4bb47 | ||
|
|
ecac92d9b3 | ||
|
|
45b6f1a6ef | ||
|
|
d61aeb09c0 | ||
|
|
8fa1843d79 | ||
|
|
3ee7277409 | ||
|
|
1139940c24 | ||
|
|
571e69bb16 | ||
|
|
a5ea35e6b7 | ||
|
|
d4ad66e2a5 | ||
|
|
886c732626 | ||
|
|
c4e0528ce8 | ||
|
|
d4f524769f | ||
|
|
defdeb1f44 | ||
|
|
3b411c657d | ||
|
|
67037c7837 | ||
|
|
dd93588539 | ||
|
|
d7bc7df3d2 | ||
|
|
49d0950a74 | ||
|
|
b3aae7824c | ||
|
|
e0dc5d4ced | ||
|
|
f7099b4555 | ||
|
|
3f8a3f3342 | ||
|
|
2b8afc3972 | ||
|
|
771341f2e9 | ||
|
|
8c928e2b01 | ||
|
|
c7139c1da1 | ||
|
|
0338cc4458 | ||
|
|
8c601f86f6 | ||
|
|
eeae378d69 | ||
|
|
f1fc4013c2 | ||
|
|
d8dafbfac3 | ||
|
|
4537c364ec | ||
|
|
0eb66aebff | ||
|
|
eb4fc9f7ce | ||
|
|
e6ddc61e86 | ||
|
|
c021fd5eb8 | ||
|
|
9b89e79f44 | ||
|
|
79ed54097a | ||
|
|
8647409f9d | ||
|
|
03d9983924 | ||
|
|
dbd65941dc | ||
|
|
b481b629b5 | ||
|
|
0b5a92ebc9 | ||
|
|
942f5dcfbd | ||
|
|
a73667610b | ||
|
|
0e98da8bca | ||
|
|
4dcd80e2ef | ||
|
|
eeec3feb1f | ||
|
|
af70a1f58b | ||
|
|
8ebd6e082e | ||
|
|
ff1e6b835c | ||
|
|
f76992d9de | ||
|
|
d11013c689 | ||
|
|
4b7a020f8e | ||
|
|
a7740757a9 | ||
|
|
e59477801e | ||
|
|
81326c996d | ||
|
|
9fbac2aeaa | ||
|
|
6248cf31c3 | ||
|
|
f3d11dbe5b | ||
|
|
a8c5d7bf2a | ||
|
|
c78d235b07 | ||
|
|
2f5a83e171 | ||
|
|
35d0e73901 | ||
|
|
434573a5f8 | ||
|
|
3810475ee8 | ||
|
|
057f5face3 | ||
|
|
021c133db3 | ||
|
|
9082a89c59 | ||
|
|
70eec78416 | ||
|
|
4e824ae7d2 | ||
|
|
1bac0903a0 | ||
|
|
d0ddadeb13 | ||
|
|
4b77ccb5f6 | ||
|
|
3ea9f7d218 | ||
|
|
84a49752d0 | ||
|
|
736513f8e1 | ||
|
|
e26887036e | ||
|
|
e9773e57b7 | ||
|
|
1d5a3b0184 | ||
|
|
f6d8a85423 | ||
|
|
8e074e2093 | ||
|
|
70bd88e2d9 | ||
|
|
9e053865e1 | ||
|
|
08dc5f5654 | ||
|
|
a1d7f2c774 | ||
|
|
ed564b904f | ||
|
|
b9aef9b317 | ||
|
|
7b9bfaaa18 | ||
|
|
edf6d48e6b | ||
|
|
ba41a27981 | ||
|
|
f72ca1d2ae | ||
|
|
e2ee43fb2d | ||
|
|
6a38654a23 | ||
|
|
3e9e1ee865 | ||
|
|
eb12b7a7ec | ||
|
|
2d23ce0084 | ||
|
|
b94aa70c65 | ||
|
|
0890dcee95 | ||
|
|
6cd44b4564 | ||
|
|
54a56a8cd4 | ||
|
|
80552bc2f1 | ||
|
|
7cc8e606f1 | ||
|
|
eaaf52ae3d | ||
|
|
e8dc787d8c | ||
|
|
c8a213ce86 | ||
|
|
0a1a4a4ad1 | ||
|
|
32644f17fe | ||
|
|
cc3196da95 | ||
|
|
e8e854cfa2 | ||
|
|
1830eb7186 | ||
|
|
d5f93b722c | ||
|
|
7271b6f585 | ||
|
|
5b8342188c | ||
|
|
acae61c26a | ||
|
|
756c272821 | ||
|
|
d71026f7ae | ||
|
|
71edc398d0 | ||
|
|
ab96026176 | ||
|
|
5942df216a | ||
|
|
319b450532 | ||
|
|
364bf3724f | ||
|
|
45cfc56e2d | ||
|
|
b40f460c59 | ||
|
|
c980d1c9d4 | ||
|
|
f961bad4d9 | ||
|
|
a0626b7166 | ||
|
|
81373d2b69 | ||
|
|
18a0ec2f67 | ||
|
|
1009a73e0a | ||
|
|
318377cb7e | ||
|
|
a8ebd94e88 | ||
|
|
3af07078c1 | ||
|
|
b917d242ea | ||
|
|
393eb54794 | ||
|
|
c58d274626 | ||
|
|
c6ce1ef87f | ||
|
|
489b1a0eb4 | ||
|
|
ea3ddee83a | ||
|
|
e80acafc0e | ||
|
|
1619b422b8 | ||
|
|
5b159a0e64 | ||
|
|
b6e80a9e3f | ||
|
|
8e15326b0f | ||
|
|
edca8e15e1 | ||
|
|
3c90fba52e | ||
|
|
dd45035cdc | ||
|
|
7010e9ffd3 | ||
|
|
54af6a5cdb | ||
|
|
8316c9cffa | ||
|
|
6d9772fa9c | ||
|
|
564a799ebf | ||
|
|
c95c915de7 | ||
|
|
7b398459db | ||
|
|
306b4e6a56 | ||
|
|
d425e7678a | ||
|
|
1bd18eece0 | ||
|
|
674c84f90a | ||
|
|
d4584c47c2 | ||
|
|
cffe0d3e80 | ||
|
|
be4452ec59 | ||
|
|
8a4b27d8ae | ||
|
|
8a34c8330f | ||
|
|
7bd0161243 | ||
|
|
5bf30ba4d2 | ||
|
|
9be00788fb | ||
|
|
253082f396 | ||
|
|
7c4d4519a6 | ||
|
|
07b0169f8e | ||
|
|
14f3d77e7e | ||
|
|
f5668478d5 | ||
|
|
17896501cf | ||
|
|
21835f574d | ||
|
|
9b029280fd | ||
|
|
4e1ff6af7d | ||
|
|
e339c9b0a9 | ||
|
|
38dc30f9b9 | ||
|
|
d291965a58 | ||
|
|
edb1d697fe | ||
|
|
8b2fbc409c | ||
|
|
4025c31570 | ||
|
|
7518fa2bf3 | ||
|
|
aa830919db | ||
|
|
2a91d26728 | ||
|
|
b4de00da0b | ||
|
|
6c8a00df5d | ||
|
|
f109575ed1 | ||
|
|
0bd06961d9 | ||
|
|
bc40d17e5a | ||
|
|
becdb36e84 | ||
|
|
1e7cd00e2d | ||
|
|
7e44663112 | ||
|
|
3053186479 | ||
|
|
b0c0cecb05 | ||
|
|
58a897dd6d | ||
|
|
28023c7530 | ||
|
|
f978d94c4b | ||
|
|
66d0ef00b3 | ||
|
|
eea735f4b2 | ||
|
|
55287f9385 | ||
|
|
851c86d032 | ||
|
|
10ff95fa6b | ||
|
|
f1b2c78695 | ||
|
|
303e7c74c3 | ||
|
|
27a6313ce1 | ||
|
|
7d39889cef | ||
|
|
afd1c6513f | ||
|
|
ec86ca0fdd | ||
|
|
4e0d799d98 | ||
|
|
fed16e5be9 | ||
|
|
5a50cd3c16 | ||
|
|
f436fa895b | ||
|
|
fa66720fc7 | ||
|
|
a7bb112c70 | ||
|
|
d117e34a79 | ||
|
|
52e103d9b3 | ||
|
|
0bedf7a901 | ||
|
|
04ec7965c1 | ||
|
|
789ed72689 | ||
|
|
eebb1adbd8 | ||
|
|
d4d75684b4 | ||
|
|
951a5dc978 | ||
|
|
7fab48ba9b | ||
|
|
b2088e7cd2 | ||
|
|
04a39c34c6 | ||
|
|
9e786bbdff | ||
|
|
6e6af1c6c4 | ||
|
|
394c048b21 | ||
|
|
94e255b198 | ||
|
|
8a41f8f476 | ||
|
|
c680df8738 | ||
|
|
49f050655c | ||
|
|
f270213bb7 | ||
|
|
6ed064eca6 | ||
|
|
d37f9a6ea1 | ||
|
|
ea7121f9d9 | ||
|
|
9af8fddaf3 | ||
|
|
b81fba82a5 | ||
|
|
fc220206d7 | ||
|
|
2686318c11 | ||
|
|
869d32785d | ||
|
|
1e14373438 | ||
|
|
e637874b85 | ||
|
|
0a1efba7d9 | ||
|
|
e7dca7ff6f | ||
|
|
93b552b33c | ||
|
|
59a36015b6 | ||
|
|
1ccf93b64b | ||
|
|
b702effb60 | ||
|
|
882218b493 | ||
|
|
2ccfadc075 | ||
|
|
9bf4bacaee | ||
|
|
e23f807e63 | ||
|
|
b959f61049 | ||
|
|
35245d5268 | ||
|
|
0781113fd9 | ||
|
|
17e6c65652 | ||
|
|
2c15be8351 | ||
|
|
fb3e5a68aa | ||
|
|
ae17432ee3 | ||
|
|
a564f49381 | ||
|
|
e300bba4de | ||
|
|
23a5377e25 | ||
|
|
ceaf19b9a1 | ||
|
|
a144096ed3 | ||
|
|
876083b085 | ||
|
|
7a4aa94b8b | ||
|
|
f7e3497bd0 | ||
|
|
df61e5d9aa | ||
|
|
d5872df7ed | ||
|
|
dcecbd4e37 | ||
|
|
9f7e02933f | ||
|
|
0b64ffb4c7 | ||
|
|
51655cffe6 | ||
|
|
fffa4cf157 | ||
|
|
c74b0a017b | ||
|
|
70301e8e91 | ||
|
|
b88b0ec271 | ||
|
|
8ac13c0d6d | ||
|
|
79c43d7c9f | ||
|
|
e9c02edfae | ||
|
|
47ea767348 | ||
|
|
718762f0e0 | ||
|
|
025da50f14 | ||
|
|
d630e34249 | ||
|
|
320b3cc885 | ||
|
|
6d5d8efe1f | ||
|
|
e75bef451b | ||
|
|
12c84196f3 | ||
|
|
749b2ba7cb | ||
|
|
828e96fa90 | ||
|
|
c3643bb1e2 | ||
|
|
59bb6d67a0 | ||
|
|
e11da6d01e | ||
|
|
ea2764c5ba | ||
|
|
120b3533bc | ||
|
|
5e00a60eaa | ||
|
|
9d20ab4dad | ||
|
|
b06b9ea379 | ||
|
|
5bc415513b | ||
|
|
9948fd4bf4 | ||
|
|
ff66f3c8dd | ||
|
|
f75f88435c | ||
|
|
4efd0c96d1 | ||
|
|
ca63bbc47c | ||
|
|
f20ed3468a | ||
|
|
9de8501277 | ||
|
|
1c68daeb39 | ||
|
|
d0968696a6 | ||
|
|
71278fe390 | ||
|
|
71064e0f92 | ||
|
|
3a93d723ea | ||
|
|
88c12ba712 | ||
|
|
cfb7fc237d | ||
|
|
84a521869b | ||
|
|
86adb641be | ||
|
|
3d340e7b9f | ||
|
|
e2d7024974 | ||
|
|
635ce0fbda | ||
|
|
a438cc75b5 | ||
|
|
bf728971c0 | ||
|
|
2e45c1ace2 | ||
|
|
ebf9d99127 | ||
|
|
773c95af58 | ||
|
|
1990a54d5d | ||
|
|
b01f950a28 | ||
|
|
d66e041db3 | ||
|
|
6b9cd02de8 | ||
|
|
572ecef291 | ||
|
|
4968d3efa6 | ||
|
|
7d4f334ec7 | ||
|
|
4a11faf7d1 | ||
|
|
88f8840ef5 | ||
|
|
ce99b1093a | ||
|
|
ab39d632fd | ||
|
|
7be473f99d | ||
|
|
2c5a845d1a | ||
|
|
86e81d3584 | ||
|
|
2120d4493e | ||
|
|
57c9c2fe90 | ||
|
|
0251d5776e | ||
|
|
b0d5834396 | ||
|
|
7b837b4468 | ||
|
|
67064b3275 | ||
|
|
6f60bb264a | ||
|
|
b560df49a3 | ||
|
|
d8b31ec4bc | ||
|
|
90fc791aac | ||
|
|
64928efb54 | ||
|
|
9ca0d7244e | ||
|
|
acb412b8f1 | ||
|
|
4dd52e6f82 | ||
|
|
23fcaaf5d0 | ||
|
|
cd9702fd03 | ||
|
|
082cfc164d | ||
|
|
b94181c796 | ||
|
|
13ad244be1 | ||
|
|
ec1ad333cd | ||
|
|
ac75e554a9 | ||
|
|
c249ed5f9f | ||
|
|
795c071e64 | ||
|
|
fc130ebfb5 | ||
|
|
a0bf4f2da0 | ||
|
|
2f57a39fcc | ||
|
|
211d6c793a | ||
|
|
79344ddad2 | ||
|
|
50a551f875 | ||
|
|
884715054b | ||
|
|
9a3bc52ffb | ||
|
|
7615f333e1 | ||
|
|
d9913ffb1e | ||
|
|
534a161dc0 | ||
|
|
83b1965733 | ||
|
|
d9df1243b6 | ||
|
|
b88365e475 | ||
|
|
090d4dc2c5 | ||
|
|
97e17a7754 | ||
|
|
a5db48c719 | ||
|
|
65d65c9e5f | ||
|
|
c5c3591471 | ||
|
|
a47c3cf004 | ||
|
|
f76c60e867 | ||
|
|
58bba637ca | ||
|
|
59063bc194 | ||
|
|
2e33b3162c | ||
|
|
1e7755b877 | ||
|
|
7b8e92ace2 | ||
|
|
e25247d0e5 | ||
|
|
93dc3338c1 | ||
|
|
fcd392eec1 | ||
|
|
81b002ad63 | ||
|
|
70feec2f5a | ||
|
|
4ddb73e8d2 | ||
|
|
ca099e077b | ||
|
|
30048e7a82 | ||
|
|
2ee05f46e7 | ||
|
|
3ea30a4461 | ||
|
|
83bae5ad74 | ||
|
|
4027e14266 | ||
|
|
a84da053a6 | ||
|
|
1b0955b959 | ||
|
|
bbcea47bfd | ||
|
|
88f656f937 | ||
|
|
148e569182 | ||
|
|
d8405e2fc8 | ||
|
|
594ce263b6 | ||
|
|
73a623cc1b | ||
|
|
e0882ab6b5 | ||
|
|
c93d9d909d | ||
|
|
8e5775dd2c | ||
|
|
dd38c0d581 | ||
|
|
f01df7bdd2 | ||
|
|
ed16e6f4e9 | ||
|
|
e9217f3261 | ||
|
|
d2e4c53c20 | ||
|
|
c402ae645e | ||
|
|
9c69a33734 | ||
|
|
47eb18142d | ||
|
|
0ca97727ad | ||
|
|
cd0cf2106a | ||
|
|
edc921bf98 | ||
|
|
b67c060211 | ||
|
|
cb5791276a | ||
|
|
d20317dd62 | ||
|
|
9cfc42156d | ||
|
|
52d8e3d54f | ||
|
|
186b56c335 | ||
|
|
006bbf8fa0 | ||
|
|
ec6ccb69dd | ||
|
|
81af09d86f | ||
|
|
4299491104 | ||
|
|
ad693eb71f | ||
|
|
02eb8f632f | ||
|
|
352236c390 | ||
|
|
cb5f5938f7 | ||
|
|
ed4583ee7a | ||
|
|
d4b09f39ad | ||
|
|
25ab2ca2dc | ||
|
|
c6714fcc4e | ||
|
|
0f558f0479 | ||
|
|
4ca87ab84e | ||
|
|
90d9c45134 | ||
|
|
193f1f8b23 | ||
|
|
3415d76b40 | ||
|
|
a5b9512fdb | ||
|
|
8db64ffd0a | ||
|
|
9a6f407a6d | ||
|
|
26c0df6e05 | ||
|
|
420d31f825 | ||
|
|
72ca147b73 | ||
|
|
f1a001a834 | ||
|
|
2b704e16c7 | ||
|
|
2c55a9d86c | ||
|
|
e4d45a1369 | ||
|
|
eebeb4a213 | ||
|
|
c70791b25c | ||
|
|
9aa6990823 | ||
|
|
67b04aa48b | ||
|
|
0dc62d67ef | ||
|
|
f3e91e22d9 | ||
|
|
f8f3f640e3 | ||
|
|
63a1fe0b9e | ||
|
|
d9dffe0151 | ||
|
|
6f28d0721e | ||
|
|
61eef2b86f | ||
|
|
74d0e4c4dc | ||
|
|
2b40b6e560 | ||
|
|
86878541f3 | ||
|
|
b0a786970f | ||
|
|
92e548462d | ||
|
|
d05e1e59dc | ||
|
|
9638153581 | ||
|
|
0e06251424 | ||
|
|
c5c04e762f | ||
|
|
e361f09691 | ||
|
|
1b1eb6636d | ||
|
|
80a2d902c0 | ||
|
|
6940eec453 | ||
|
|
aaf27f2d60 | ||
|
|
9cb1f2787c | ||
|
|
c047ec8588 | ||
|
|
2b54468923 | ||
|
|
e8f257d2cf | ||
|
|
66763e537d | ||
|
|
0eee296664 | ||
|
|
2891133c5c | ||
|
|
6231775997 | ||
|
|
61ff4d50fd | ||
|
|
13560e3b53 | ||
|
|
52ae852953 | ||
|
|
7761706fb5 | ||
|
|
d25437c8e0 | ||
|
|
6883ef1e9f | ||
|
|
6bb9dd1028 | ||
|
|
d6278541dd | ||
|
|
5977598c71 | ||
|
|
35706d71f7 | ||
|
|
c38f0900d1 | ||
|
|
23ed82c0ba | ||
|
|
ea4b82f11e | ||
|
|
8cfff4299a | ||
|
|
56250bea63 | ||
|
|
d4d376c8c6 | ||
|
|
0146357eed | ||
|
|
9759264aec | ||
|
|
c17465a2a7 | ||
|
|
dc3795d1cd | ||
|
|
fdf2b26fdf | ||
|
|
eff0012ea1 | ||
|
|
d74186e93c | ||
|
|
fe108bb354 | ||
|
|
9fb556f180 | ||
|
|
f1db204e0d | ||
|
|
fd35c8d862 | ||
|
|
4d134e5087 | ||
|
|
1e3b4e34e5 | ||
|
|
95a02bece7 | ||
|
|
62ffb786b2 | ||
|
|
4d41ed60c9 | ||
|
|
fc8624c93b | ||
|
|
800a77b2e0 | ||
|
|
361443d845 | ||
|
|
7db9a6730a | ||
|
|
5a784e6c9b | ||
|
|
91c16a319c | ||
|
|
47ebfec59e | ||
|
|
f582469ba2 | ||
|
|
06bc5e074a | ||
|
|
d4c3dc4eab | ||
|
|
cfeb07c65f | ||
|
|
ced56ec4cd | ||
|
|
3cf215dbd0 | ||
|
|
f237a8d29a | ||
|
|
4d5a1a152f | ||
|
|
6735bf3e76 | ||
|
|
85adc466b3 | ||
|
|
afc02a856d | ||
|
|
21d6b02410 | ||
|
|
5b0b719ce9 | ||
|
|
9ce0315c3e | ||
|
|
6fe34ec618 | ||
|
|
05a30d0129 | ||
|
|
6cf2c3459c | ||
|
|
e87513bb45 | ||
|
|
0f5c1b8e8e | ||
|
|
c109ac76a7 | ||
|
|
d7bca1c232 | ||
|
|
0a034f9acb | ||
|
|
98774c408c | ||
|
|
802036f693 | ||
|
|
5bb9ac6fcd | ||
|
|
fcc02ed600 | ||
|
|
1e45d14ec3 | ||
|
|
97027e657e | ||
|
|
487fec0258 | ||
|
|
b25bc72f3e | ||
|
|
d086e4d692 | ||
|
|
8d2d66abb9 | ||
|
|
6ca1269261 | ||
|
|
d374da0481 | ||
|
|
83242e1fa9 | ||
|
|
35fa4e7d8f | ||
|
|
5022e96a48 | ||
|
|
1779c8f84e | ||
|
|
74d9791f03 | ||
|
|
a6cb96c758 | ||
|
|
b05463ad6c | ||
|
|
f229fbc4b1 | ||
|
|
8d4f72c691 | ||
|
|
1d470f87d4 | ||
|
|
04260a7f45 | ||
|
|
3c03199ea3 | ||
|
|
acd68f86d1 | ||
|
|
23e312f2ba | ||
|
|
3931903018 | ||
|
|
8c680da98c | ||
|
|
21e682f54d | ||
|
|
70593c2b6a | ||
|
|
6b64667767 | ||
|
|
96d1777132 | ||
|
|
f6bd725633 | ||
|
|
dd7e2d933f | ||
|
|
97177e379e | ||
|
|
8df00a18e8 | ||
|
|
6668fa5505 | ||
|
|
c348ffa34e | ||
|
|
275e1be6be | ||
|
|
c18924a350 | ||
|
|
8325893369 | ||
|
|
66fd90332d | ||
|
|
4dad9e3478 | ||
|
|
0c005bb3ff | ||
|
|
57b9a1b4c7 | ||
|
|
b29b1b3630 | ||
|
|
3a071ea071 | ||
|
|
25efe15329 | ||
|
|
d544d69524 | ||
|
|
bcc0fc9e00 | ||
|
|
7f2d2d6588 | ||
|
|
2e70eb6718 | ||
|
|
b7601fded3 | ||
|
|
13ace49230 | ||
|
|
229ecc9ba7 | ||
|
|
602f761552 | ||
|
|
f271c7a74e | ||
|
|
c47242e40e | ||
|
|
887f2f9a52 | ||
|
|
6b528a6d99 | ||
|
|
7ff8564077 | ||
|
|
77e79653ba | ||
|
|
4665503caa | ||
|
|
1f922362a1 | ||
|
|
f5e095f345 | ||
|
|
a392fc43f9 | ||
|
|
d444e8896c | ||
|
|
935d42af74 | ||
|
|
a9e15c2d33 | ||
|
|
b5ae52a3b6 | ||
|
|
abf405fd20 | ||
|
|
3215eb9a8a | ||
|
|
f2ddd697e9 | ||
|
|
9e6fc59505 | ||
|
|
2dc0e10c34 | ||
|
|
2a086c8b64 | ||
|
|
cf9e3a5f1d | ||
|
|
91616396b7 | ||
|
|
300e3fd4d3 | ||
|
|
373e11bc41 | ||
|
|
4cb140512a | ||
|
|
bb27eab819 | ||
|
|
d99dd5f19f | ||
|
|
d60d350fcb | ||
|
|
b1e7e407a7 | ||
|
|
4e7d338144 | ||
|
|
4528541b3f | ||
|
|
a829dc72bc | ||
|
|
9e58ec0f80 | ||
|
|
606f52e493 | ||
|
|
769f6ea976 | ||
|
|
b6395aa463 | ||
|
|
3f1d6238ff | ||
|
|
89eed17839 | ||
|
|
e990c3648e | ||
|
|
67f9be5d4d | ||
|
|
8650a147aa | ||
|
|
8490f8d90b | ||
|
|
8f1c7bf40a | ||
|
|
ec87b64d0c | ||
|
|
27eee4c8fa | ||
|
|
af32152bcc | ||
|
|
e72edb95b5 | ||
|
|
efed143a11 | ||
|
|
99f64d7565 | ||
|
|
32ef472b0c | ||
|
|
7ade1b9a52 | ||
|
|
cc664d1280 | ||
|
|
84874601a9 | ||
|
|
b47d528bf0 | ||
|
|
59612c45ea | ||
|
|
e8be6f674e | ||
|
|
33554dd9cc | ||
|
|
4dd73930e3 | ||
|
|
af14c33e39 | ||
|
|
7e9312a93c | ||
|
|
4048ba18f3 | ||
|
|
3ed563d3f1 | ||
|
|
11aef9f054 | ||
|
|
556a8e7176 | ||
|
|
465066c04c | ||
|
|
668eaa1a7c | ||
|
|
0bed673340 | ||
|
|
13f606999c | ||
|
|
b0677ca006 | ||
|
|
22846db1a3 | ||
|
|
c9b634a274 | ||
|
|
c37a492cb7 | ||
|
|
92f663c753 | ||
|
|
35a2f9bf2b | ||
|
|
2dcf4b23b7 | ||
|
|
22b2f30ffb | ||
|
|
9a7215a2a2 | ||
|
|
6d4cc6efcb | ||
|
|
45a0c54fb8 | ||
|
|
e8c9c8b90c | ||
|
|
367fcad53c | ||
|
|
3a6c5b0cd5 | ||
|
|
375adab087 | ||
|
|
fbf0f98c3a | ||
|
|
711342829b | ||
|
|
daf3be6346 | ||
|
|
06edf9351a | ||
|
|
ead64864f4 | ||
|
|
8521387312 | ||
|
|
3e5b47185e | ||
|
|
b5dbb7da90 | ||
|
|
03886905bd | ||
|
|
2b688a99ff | ||
|
|
f01282f12f | ||
|
|
5e5fca9722 | ||
|
|
2ff32d3b90 | ||
|
|
3b72bd18f0 | ||
|
|
fcb7f89001 | ||
|
|
38cebf52c3 | ||
|
|
56983b2553 | ||
|
|
04b347b968 | ||
|
|
08a87ea112 | ||
|
|
67e8046af6 | ||
|
|
dc11fad342 | ||
|
|
8f9857e72d | ||
|
|
76bcbc632e | ||
|
|
e04d88977e | ||
|
|
3e613a8b80 | ||
|
|
1f364168e8 | ||
|
|
07439cfeeb | ||
|
|
94eda92d21 | ||
|
|
15715cec10 | ||
|
|
3662bb8e84 | ||
|
|
14fc75be44 | ||
|
|
a489e0845b | ||
|
|
acf7a23965 | ||
|
|
a96a2fd825 | ||
|
|
d455653f11 | ||
|
|
8579ae99a0 | ||
|
|
da5cb3d905 | ||
|
|
81315e3db4 | ||
|
|
8a880a3f89 | ||
|
|
d5da9a80f6 | ||
|
|
670966be57 | ||
|
|
5879690fc0 | ||
|
|
ff054d44b7 | ||
|
|
e909cfc744 | ||
|
|
b51ca84a8f | ||
|
|
3a9af0480d | ||
|
|
ebd0a8096c | ||
|
|
a1f2f8d944 | ||
|
|
0a4dc6961d | ||
|
|
2e196fc41b | ||
|
|
844a7b5d76 | ||
|
|
ce343ffcdb | ||
|
|
25972aaa29 | ||
|
|
0fe8c1f121 | ||
|
|
cb37cf0889 | ||
|
|
b5721a7e36 | ||
|
|
93b40ad867 | ||
|
|
a3e5e40385 | ||
|
|
e13ed6f46e | ||
|
|
65dffbbe3a | ||
|
|
16dafc9c57 | ||
|
|
bcd2f95e4d | ||
|
|
51ae7fee7e | ||
|
|
2eb276f995 | ||
|
|
6a98f9947a | ||
|
|
66cbd1a30e | ||
|
|
c8d1b56d9e | ||
|
|
457ce2958e | ||
|
|
b7b6d5672e | ||
|
|
7dac6197e3 | ||
|
|
ea40b439ee | ||
|
|
7061f7f26a | ||
|
|
de1342a5d7 | ||
|
|
2f976dbeb3 | ||
|
|
e37e242bae | ||
|
|
78c676e858 | ||
|
|
9e74ef9e37 | ||
|
|
1740be22ae | ||
|
|
35c499a074 | ||
|
|
048093522a | ||
|
|
66c696bfec | ||
|
|
5dc5082c4e | ||
|
|
42f79630e6 | ||
|
|
f8ccf0a91e | ||
|
|
b924c1a80c | ||
|
|
42a6340a95 | ||
|
|
8b2b1d453d | ||
|
|
e70207fbac | ||
|
|
846db416ec | ||
|
|
84288aafe9 | ||
|
|
d2d4318659 | ||
|
|
324c3000a0 | ||
|
|
fc6d1d2ab8 | ||
|
|
7fb12fa5b3 | ||
|
|
791e53a935 | ||
|
|
3a705c3e5f | ||
|
|
ca61291995 | ||
|
|
a875557000 | ||
|
|
517590883c | ||
|
|
ffa58f71f0 | ||
|
|
9017f1488f | ||
|
|
c6bac3a247 | ||
|
|
a7e48e8af0 | ||
|
|
41b73da329 | ||
|
|
48d4016f8d | ||
|
|
ed3624ce7b | ||
|
|
58eebd8bfd | ||
|
|
cc95200289 | ||
|
|
7035993a3f | ||
|
|
663b10f767 | ||
|
|
922e45a80d | ||
|
|
3dca3752bc | ||
|
|
336ca104e9 | ||
|
|
7492e7d450 | ||
|
|
7440702fea | ||
|
|
e568f84dcb | ||
|
|
d043161bdd | ||
|
|
9768b80736 | ||
|
|
93185e7e84 | ||
|
|
18a717e90a | ||
|
|
99ce255da8 | ||
|
|
770e328865 | ||
|
|
2776eaf640 | ||
|
|
2d825e4095 | ||
|
|
64cac6cd5a | ||
|
|
8d7eccf92e | ||
|
|
01cd80cc2f | ||
|
|
801e218912 | ||
|
|
c1cb19a139 | ||
|
|
419f4a354f | ||
|
|
569e884a2c | ||
|
|
647e9f6a14 | ||
|
|
a24cffa9df | ||
|
|
58d0921696 | ||
|
|
2b3e39cbb8 | ||
|
|
689e5eecc4 | ||
|
|
36917c0d18 | ||
|
|
4421766850 | ||
|
|
2d2af55849 | ||
|
|
906fef89f9 | ||
|
|
479a468187 | ||
|
|
813e8deb8f | ||
|
|
4c4259ed90 | ||
|
|
b186958422 | ||
|
|
83b41e1479 | ||
|
|
97477644fd | ||
|
|
f3e4f0d1fb | ||
|
|
4175369f96 | ||
|
|
f2fcfe582f | ||
|
|
b2d546be2f | ||
|
|
a7db0dd75e | ||
|
|
94127c1b76 | ||
|
|
220690d4d0 | ||
|
|
fb3ffe679b | ||
|
|
b38f7fe4f5 | ||
|
|
110eb2b072 | ||
|
|
d474e6b8bb | ||
|
|
5b074f4c1c | ||
|
|
8e4ab4d398 | ||
|
|
7eb934d5d3 | ||
|
|
c3b655f675 | ||
|
|
068efaeba7 | ||
|
|
ff7a0f1ae6 | ||
|
|
8b26deb477 | ||
|
|
21fedd0d50 | ||
|
|
ecd4a5dedd | ||
|
|
047ee71259 | ||
|
|
1b97eb777c | ||
|
|
cd31c4e513 | ||
|
|
1582f5a7bd | ||
|
|
32facbbc74 | ||
|
|
7696b9d69c | ||
|
|
d2e1d5aacb | ||
|
|
e70affe6c3 | ||
|
|
d38f9c39e4 | ||
|
|
f7209ef32c | ||
|
|
1f10ba936a | ||
|
|
0332975a9a | ||
|
|
369c4f77a0 | ||
|
|
d8a284ebb9 | ||
|
|
dca62ce725 | ||
|
|
1c64ebd05b | ||
|
|
0727c83a43 | ||
|
|
eb4a8caefc | ||
|
|
253efc6c6d | ||
|
|
d8be06c165 | ||
|
|
e02baac897 | ||
|
|
46f19b6079 | ||
|
|
2f3ba53a89 | ||
|
|
b763e06560 | ||
|
|
2447ea4fb0 | ||
|
|
75f86352be | ||
|
|
19dd58d1d3 | ||
|
|
68843db5b8 | ||
|
|
586c2eb9b0 | ||
|
|
156cdf812d | ||
|
|
5e6d9749fb | ||
|
|
2392aeeb0c | ||
|
|
6d89addb67 | ||
|
|
5a29b05e88 | ||
|
|
90b46f4c0f | ||
|
|
1281af0994 | ||
|
|
b31069866a | ||
|
|
00f896bbb4 | ||
|
|
cd5c03926f | ||
|
|
11218913a4 | ||
|
|
0a039e4f53 | ||
|
|
7e37b3d760 | ||
|
|
d577c229e3 | ||
|
|
6cd74946e4 | ||
|
|
d34b6428ae | ||
|
|
e242e3f371 | ||
|
|
4165004491 | ||
|
|
d290d5fbf4 | ||
|
|
81c99209ce | ||
|
|
1962aac95b | ||
|
|
60a66585f3 | ||
|
|
d7f3842a30 | ||
|
|
81be883bee | ||
|
|
0dfcb225e0 | ||
|
|
06f95efd52 | ||
|
|
0dae411333 | ||
|
|
c1e1b6f29a | ||
|
|
a511bdbcd8 | ||
|
|
b0037faa11 | ||
|
|
6dc6a7a29c | ||
|
|
fd6192a88e | ||
|
|
fcb8e1f8c9 | ||
|
|
2da04fed3d | ||
|
|
00abdccc13 | ||
|
|
9f25644645 | ||
|
|
83b84dfff7 | ||
|
|
c0f5f0567b | ||
|
|
f53a54a126 | ||
|
|
e3ac4433e1 | ||
|
|
1e831513a0 | ||
|
|
a40a6e8023 | ||
|
|
22982178e2 | ||
|
|
63e2854d7a | ||
|
|
7117580446 | ||
|
|
691562324b | ||
|
|
f0e6e20732 | ||
|
|
67ab3e1e01 | ||
|
|
a048ab3327 | ||
|
|
af1be79f78 | ||
|
|
81366a1e14 | ||
|
|
21c4e2eeda | ||
|
|
b866338197 | ||
|
|
98e19db3fc | ||
|
|
5221d871a0 | ||
|
|
d48c93938f | ||
|
|
0aa3583412 | ||
|
|
38adbb7386 | ||
|
|
0ecdb07a0a | ||
|
|
5191777fca | ||
|
|
e1feff66b4 | ||
|
|
e96f0e24b3 | ||
|
|
891b2580c0 | ||
|
|
8fa477a3f2 | ||
|
|
48079df235 | ||
|
|
4bd796c84b | ||
|
|
efa38ea80a | ||
|
|
b0be025f93 | ||
|
|
08bc3170ad | ||
|
|
b126a97e23 | ||
|
|
505940d9b4 | ||
|
|
41fbedbdb9 | ||
|
|
00379f8cd0 | ||
|
|
52e9772ef9 | ||
|
|
634a9e7a6c | ||
|
|
8383aeeddd | ||
|
|
308c07c2b3 | ||
|
|
ad352beb24 | ||
|
|
1e59b75d08 | ||
|
|
bfef8b125c | ||
|
|
b3f1bad6b7 | ||
|
|
8e3e38b72f | ||
|
|
714f21913b | ||
|
|
6e1733821c | ||
|
|
d5c285bb63 | ||
|
|
2be734f137 | ||
|
|
67ab92359f | ||
|
|
89184b97dd | ||
|
|
466eac71a1 | ||
|
|
ce35c6232e | ||
|
|
2e51375c0f | ||
|
|
e38b34e391 | ||
|
|
2e0af3c3fe | ||
|
|
ab1550e24b | ||
|
|
7d57af98fd | ||
|
|
f1cb6b7b8e | ||
|
|
2cb03f1671 | ||
|
|
da68b22601 | ||
|
|
c68f1b8a03 | ||
|
|
02ae5e301d | ||
|
|
0dc7a4af2d | ||
|
|
4d8bb5a844 | ||
|
|
a8ffdecef0 | ||
|
|
adf62cc5f5 | ||
|
|
4aaafdf106 | ||
|
|
95e5f06554 | ||
|
|
361604d7f6 | ||
|
|
97292d5a7f | ||
|
|
3a552823c8 | ||
|
|
a36c06ff2f | ||
|
|
fe7cce1726 | ||
|
|
b4e5313160 | ||
|
|
151fb40e23 | ||
|
|
a111341293 | ||
|
|
4df7554d81 | ||
|
|
e58b0c219f | ||
|
|
e551c98ca4 | ||
|
|
0bd954e0c1 | ||
|
|
23f13ca87e | ||
|
|
2f66df36a7 | ||
|
|
03cc62f4df | ||
|
|
31fb23b83b | ||
|
|
445c87812d | ||
|
|
25b3ffbb6e | ||
|
|
358f5c6046 | ||
|
|
4fdc38b53f | ||
|
|
9fc669d897 | ||
|
|
f40f868429 | ||
|
|
43bdd23b3c | ||
|
|
0bf12f0965 | ||
|
|
5434605984 | ||
|
|
19a1bf0c0e | ||
|
|
16adb81cd2 | ||
|
|
115b77682b | ||
|
|
82839cc500 | ||
|
|
5dca460399 | ||
|
|
0eeb68737c | ||
|
|
389e21aca7 | ||
|
|
1eec2905b1 | ||
|
|
7823368ebc | ||
|
|
8b0f5ff91e | ||
|
|
f45a4d06a3 | ||
|
|
d16a197651 | ||
|
|
95263720f3 | ||
|
|
56f05c54f9 | ||
|
|
b20c5acf75 | ||
|
|
33f296da7c | ||
|
|
429ac3c072 | ||
|
|
4b5a555c1e | ||
|
|
278d64d800 | ||
|
|
6ceb08d41d | ||
|
|
0e513614c3 | ||
|
|
b8ac0819aa | ||
|
|
8380da7a10 | ||
|
|
cb7922fe15 | ||
|
|
3b6cfef67d | ||
|
|
b20bb2d023 | ||
|
|
f8decaac41 | ||
|
|
d7674c15b5 | ||
|
|
9d1c6eaa7f | ||
|
|
1927a94879 | ||
|
|
590b877f76 | ||
|
|
6c93862891 | ||
|
|
cd9289bcdd | ||
|
|
c294e1f6df | ||
|
|
9090cc70bf | ||
|
|
244e2a9e8d | ||
|
|
cc2b2921d9 | ||
|
|
88339864f7 | ||
|
|
f0ad9083a5 | ||
|
|
e66194fb9d | ||
|
|
042693bf53 | ||
|
|
4a8cecd225 | ||
|
|
1a9003c999 | ||
|
|
e35f09ea2f | ||
|
|
056d66b865 | ||
|
|
ceafaf5e2e | ||
|
|
5207e697dd | ||
|
|
39a5da9a1b | ||
|
|
4ed99e026f | ||
|
|
cdff15dade | ||
|
|
16f2b16d22 | ||
|
|
3efde7a2e3 | ||
|
|
032bcec313 | ||
|
|
a5d5ed8524 | ||
|
|
c1e5d346c8 | ||
|
|
0a004b8f7e | ||
|
|
ffcd5b2f2c | ||
|
|
11ed680b5a | ||
|
|
3abc22a855 | ||
|
|
ef9d6203c0 | ||
|
|
016df27726 | ||
|
|
246ce114e2 | ||
|
|
f3767417b0 | ||
|
|
67fb62ec52 | ||
|
|
27f2dcc8d5 | ||
|
|
9f37393a69 | ||
|
|
a7af6ecfc0 | ||
|
|
f772ab5fcc | ||
|
|
6076954881 | ||
|
|
581bb74b9c | ||
|
|
81a8c93db4 | ||
|
|
6d8e162df3 | ||
|
|
9f44b46186 | ||
|
|
e15f826190 | ||
|
|
f5ea2c0345 | ||
|
|
fff0051540 | ||
|
|
0fd3df021a | ||
|
|
44bbcfeebb | ||
|
|
5ae8e4c391 | ||
|
|
1e039cefcb | ||
|
|
eeafd84625 | ||
|
|
6955ad861c | ||
|
|
9fff6d70fe | ||
|
|
742e718c43 | ||
|
|
d3020d591c | ||
|
|
ab44402886 | ||
|
|
13cf87fa7a | ||
|
|
c9a2ccb9f9 | ||
|
|
3f78a5d595 | ||
|
|
cdfdcba477 | ||
|
|
1dbbfb5112 | ||
|
|
d528d3854b | ||
|
|
f6b5467663 | ||
|
|
77e22b355b | ||
|
|
74b2b1d4cf | ||
|
|
9736d6c3e4 | ||
|
|
4c8f1be770 | ||
|
|
3f88293d8e | ||
|
|
40828d8f57 | ||
|
|
c97d67e4fc | ||
|
|
e3265ace4b | ||
|
|
2e3afcbe45 | ||
|
|
236ac800d6 | ||
|
|
d395c47d4d | ||
|
|
178944b3da | ||
|
|
61457cbba8 | ||
|
|
a74d4ab0c6 | ||
|
|
3f561f004a | ||
|
|
26a3d1b3be | ||
|
|
a4e619fba2 | ||
|
|
f6230e76fd | ||
|
|
5a7f35c6ab | ||
|
|
1c5c3eba74 | ||
|
|
59569a9bfc | ||
|
|
9c7e5ae8dd | ||
|
|
bd0049ef57 | ||
|
|
f936f443ea | ||
|
|
4d85237f44 | ||
|
|
1cf8a96c3d | ||
|
|
99242384f5 | ||
|
|
aaed05bef8 | ||
|
|
857e3fb4a1 | ||
|
|
c0ae7f42d9 | ||
|
|
37605ca2b8 | ||
|
|
89ecbda923 | ||
|
|
dc24cabcbf | ||
|
|
ae2d1a438c | ||
|
|
b85917140d | ||
|
|
4373e81508 | ||
|
|
d1293259e7 | ||
|
|
381304867b | ||
|
|
1b4b5752aa | ||
|
|
94b3ae3715 | ||
|
|
b478099a2b | ||
|
|
26d8419678 | ||
|
|
1eb2cbf275 | ||
|
|
e1f3c3ad1c | ||
|
|
98c3787ed4 | ||
|
|
97fcce87c4 | ||
|
|
1536996a02 | ||
|
|
542bc66f86 | ||
|
|
206d1639b5 | ||
|
|
3a9ae627db | ||
|
|
831b5a8ba3 | ||
|
|
9b4f423afa | ||
|
|
e7ce5bcdb6 | ||
|
|
73bbb0d3dd | ||
|
|
1e5d2ef239 | ||
|
|
07b25b78ee | ||
|
|
e67518d0f0 | ||
|
|
c1b7c46eca | ||
|
|
d2d43aea2e | ||
|
|
1fa83a2d84 | ||
|
|
6d1f698882 | ||
|
|
3c4a34daaa | ||
|
|
590b487711 | ||
|
|
7344c934a4 | ||
|
|
9a67bc0f49 | ||
|
|
cbc5dc79a0 | ||
|
|
1fcde1cd4b | ||
|
|
a16e398feb | ||
|
|
25ca71a97a | ||
|
|
23618de189 | ||
|
|
43455723f6 | ||
|
|
d646240c63 | ||
|
|
d60ee9307d | ||
|
|
c2915d909d | ||
|
|
3187969200 | ||
|
|
30f18cd947 | ||
|
|
181a1d2f1a | ||
|
|
3404d267f7 | ||
|
|
0af6b92e9f | ||
|
|
c87a2bff23 | ||
|
|
32a42cf9ec | ||
|
|
e2189ea877 | ||
|
|
fe31d692f2 | ||
|
|
84c2ac918b | ||
|
|
6cf97aa2fc | ||
|
|
b58a561863 | ||
|
|
06c13804a4 | ||
|
|
bf8e88f76b | ||
|
|
84528b5251 | ||
|
|
f76428b668 | ||
|
|
d8abdf1a2e | ||
|
|
f9f83660f5 | ||
|
|
ddfd97c395 | ||
|
|
f658ba29c9 | ||
|
|
57cf22af43 | ||
|
|
eaea379532 | ||
|
|
2ac3829b6f | ||
|
|
29e3cf4a79 | ||
|
|
d84e2e4e23 | ||
|
|
02b8d39791 | ||
|
|
f7a74edc6e | ||
|
|
8feaf0795f | ||
|
|
87a857220f | ||
|
|
403550af5f | ||
|
|
d6ffdddfc6 | ||
|
|
7663df024c | ||
|
|
704c735bc5 | ||
|
|
ec72e9f7d7 | ||
|
|
f95ada602c | ||
|
|
7c10272d03 | ||
|
|
3c3c64a11a | ||
|
|
722c16e925 | ||
|
|
4a799cf45f | ||
|
|
16acf2d46c | ||
|
|
413c14a1dc | ||
|
|
826f37e4b9 | ||
|
|
39654e498c | ||
|
|
e3432ced3c | ||
|
|
917ae1daab | ||
|
|
a09086bfc2 | ||
|
|
2ac38e6639 | ||
|
|
3dc54790cc | ||
|
|
ce780c5175 | ||
|
|
35a76b4a43 | ||
|
|
999f990870 | ||
|
|
e050a62b0c | ||
|
|
1cd278afcf | ||
|
|
208089a5cd | ||
|
|
0c66c256b3 | ||
|
|
66e8154243 | ||
|
|
ff4db64802 | ||
|
|
4c29790b6c | ||
|
|
3efc4807cf | ||
|
|
a3b543cdd2 | ||
|
|
a12eedfa93 | ||
|
|
a97125d733 | ||
|
|
7b5d2fc053 | ||
|
|
0e587c5b3a | ||
|
|
6ea9a07a3d | ||
|
|
090a47b17f | ||
|
|
f79b326010 | ||
|
|
4c6aa708c1 | ||
|
|
7ac66a298b | ||
|
|
9e1baa9521 | ||
|
|
c1870b4ef8 | ||
|
|
a9b068c89e | ||
|
|
dab7d5030e | ||
|
|
120a314a96 | ||
|
|
bd4305dd13 | ||
|
|
d8b7214a2b | ||
|
|
95122ffcf8 | ||
|
|
3b1b0bc92f | ||
|
|
49f789c404 | ||
|
|
9cf447d422 | ||
|
|
1dc10b57c6 | ||
|
|
3d11422191 | ||
|
|
fa0fc14c25 | ||
|
|
22cdb7e721 | ||
|
|
d215657641 | ||
|
|
df33b9388f | ||
|
|
ac53f16098 | ||
|
|
36d6342e77 | ||
|
|
72e7822616 | ||
|
|
ea8f990172 | ||
|
|
8003ade509 | ||
|
|
3795232b2c | ||
|
|
dda26098f4 | ||
|
|
ae042d98f4 | ||
|
|
649faa3371 | ||
|
|
cea0c3f628 | ||
|
|
dce2ebbafb | ||
|
|
c80d703acf | ||
|
|
f0418b8d2a | ||
|
|
f363ae0f1e | ||
|
|
24d3801316 | ||
|
|
2a3f2c6dbd | ||
|
|
318a6e2924 | ||
|
|
800311fed8 | ||
|
|
e49d45bf4f | ||
|
|
356bd9c635 | ||
|
|
b400ea6d33 | ||
|
|
7352f9094a | ||
|
|
c90e8bd356 | ||
|
|
9f79073d14 | ||
|
|
249f8a4673 | ||
|
|
fe8ec70159 | ||
|
|
1f5375fed2 | ||
|
|
804d47df7b | ||
|
|
c6dfc1ceb2 | ||
|
|
b3727f6821 | ||
|
|
bc538da8ea | ||
|
|
465e25adff | ||
|
|
809fc513ea | ||
|
|
ea101baf0d | ||
|
|
19f88d6e6d | ||
|
|
7499d05bbc | ||
|
|
de2e3fc070 | ||
|
|
9b85293349 | ||
|
|
bee34c064b | ||
|
|
7ba64ce136 | ||
|
|
d80d2c5cda | ||
|
|
2419b93682 | ||
|
|
740b3f0c49 | ||
|
|
0925379a6d | ||
|
|
0c89087579 | ||
|
|
0565082309 | ||
|
|
cf7fcb07a1 | ||
|
|
ad99d21cdf | ||
|
|
3ad4da4f49 | ||
|
|
62238e0f81 | ||
|
|
2928f157fa | ||
|
|
cacb64b2ca | ||
|
|
1f65c4723d | ||
|
|
c2d4923706 | ||
|
|
5ea07389b4 | ||
|
|
8c56bb51ed | ||
|
|
7c8057dbc6 | ||
|
|
841b9ae668 | ||
|
|
f847a39eaa | ||
|
|
f2eafb99d4 | ||
|
|
847becccfe | ||
|
|
536c2483b0 | ||
|
|
af80228bcd | ||
|
|
334b086efb | ||
|
|
bddce9872f | ||
|
|
3d4e544573 | ||
|
|
7fe68a1c4a | ||
|
|
8693b6ce81 | ||
|
|
200b3f7092 | ||
|
|
69afbde352 | ||
|
|
052fb65518 | ||
|
|
c2ec6978c4 | ||
|
|
941e25d7a7 | ||
|
|
0bddb07ba0 | ||
|
|
869697352d | ||
|
|
9757fdb16b | ||
|
|
4d718177b6 | ||
|
|
2dff30f2cd | ||
|
|
8b5128510b | ||
|
|
1efa8d030b | ||
|
|
c855a107ab | ||
|
|
a379279956 | ||
|
|
3fb38ae682 | ||
|
|
ee29d4289c | ||
|
|
eeccc8490d | ||
|
|
2ae735db74 | ||
|
|
248d6c57c9 | ||
|
|
5eba9bfc14 | ||
|
|
7bc14a526d | ||
|
|
769521c978 | ||
|
|
bc71ef6a18 | ||
|
|
422ab9b835 | ||
|
|
410f9b1acf | ||
|
|
f6b3cb4b53 | ||
|
|
6a72aab106 | ||
|
|
f3695e18ac | ||
|
|
f64846c690 | ||
|
|
79bb27d2a7 | ||
|
|
f0dba0ca0e | ||
|
|
b70627bd3f | ||
|
|
91b894958c | ||
|
|
ef10e83f1c | ||
|
|
e958cb665e | ||
|
|
8413ec92d5 | ||
|
|
92d77b610d | ||
|
|
42f445b1d2 | ||
|
|
ca886f4c0c | ||
|
|
531ac8fc50 | ||
|
|
628c8f5ff1 | ||
|
|
b63037caf3 | ||
|
|
19127cb09d | ||
|
|
004e0a4265 | ||
|
|
9bbc23d550 | ||
|
|
77cea9f74e | ||
|
|
0bc57c145a | ||
|
|
92545309e1 | ||
|
|
bdc2fe8e69 | ||
|
|
1d3c5c85ec | ||
|
|
cd14da0454 | ||
|
|
73ccaac656 | ||
|
|
b19df1be9f | ||
|
|
f10bfe8e8d | ||
|
|
3418fdf822 | ||
|
|
f31098bf15 | ||
|
|
27ec3db33d | ||
|
|
8b618df712 | ||
|
|
a87d428563 | ||
|
|
d778138f92 | ||
|
|
1197b1c59a | ||
|
|
e5cf4e8cea | ||
|
|
4bbfb1852f | ||
|
|
d88ecc8505 | ||
|
|
6d1ef8257f | ||
|
|
5167eabb5c | ||
|
|
c8f08f61e5 | ||
|
|
9ed4822a93 | ||
|
|
0db2fba9fa | ||
|
|
117ee3d7bc | ||
|
|
c30261f9b8 | ||
|
|
5bd0fcf866 | ||
|
|
c888a09a40 | ||
|
|
c88818aa55 | ||
|
|
bafbdc8a5a | ||
|
|
a04c9f144a | ||
|
|
3301d91ae7 | ||
|
|
6c4ed98ad4 | ||
|
|
273894b734 | ||
|
|
2fd3b13d86 | ||
|
|
e3ec921e3d | ||
|
|
8e4ce46891 | ||
|
|
9c50f45866 | ||
|
|
2b3733a752 | ||
|
|
ae709d210e | ||
|
|
8f94bceaf2 | ||
|
|
36fbb71e1d | ||
|
|
464a1fe51c | ||
|
|
4067caa144 | ||
|
|
c576b0f65c | ||
|
|
6156ad254f | ||
|
|
3b0465aafe | ||
|
|
d7da7a3fd1 | ||
|
|
3fb515c73b | ||
|
|
8ef5fbc84b | ||
|
|
51f1c281e7 | ||
|
|
d93e9170ad | ||
|
|
418d4d5019 | ||
|
|
bf6c569c01 | ||
|
|
6e1a994971 | ||
|
|
0ed44d4afb | ||
|
|
92cc1c7718 | ||
|
|
b4347ff53d | ||
|
|
3e2b4418ef | ||
|
|
691264c2c3 | ||
|
|
a21b2acf6f | ||
|
|
54a3052fdb | ||
|
|
33fdd2969a | ||
|
|
2f146c9648 | ||
|
|
d7ead4cc3d | ||
|
|
f5b275d0d1 | ||
|
|
5b18502378 | ||
|
|
792fa2e9ca | ||
|
|
c1f4400441 | ||
|
|
18e813b3ed | ||
|
|
5bc66f4c76 | ||
|
|
df7d5e9c8e | ||
|
|
a9c68d2942 | ||
|
|
541afd807e | ||
|
|
598a391c12 | ||
|
|
a9c536322e | ||
|
|
2635c1798d | ||
|
|
92f1596336 | ||
|
|
9d41132be6 | ||
|
|
5c367ef940 | ||
|
|
423d38c94e | ||
|
|
4ad20fd7d6 | ||
|
|
7cff83168f | ||
|
|
7ea109aaf8 | ||
|
|
beb29502a7 | ||
|
|
1fed0a1264 | ||
|
|
d2182ad49c | ||
|
|
bb01e2a4d4 | ||
|
|
b46c0bb05d | ||
|
|
192ef621f6 | ||
|
|
4a43abc905 | ||
|
|
07dfb2e9df | ||
|
|
2f8beff993 | ||
|
|
72ec9a2b70 | ||
|
|
1ce6979910 | ||
|
|
c0794aff76 | ||
|
|
371ed047c6 | ||
|
|
b4cc04f32b | ||
|
|
42596623a9 | ||
|
|
345c809a23 | ||
|
|
fbe169f06d | ||
|
|
4f5fc25195 | ||
|
|
e1f3fb6ac0 | ||
|
|
9915d3c9ac | ||
|
|
0bbed86241 | ||
|
|
5076d03143 | ||
|
|
52b5ef7647 | ||
|
|
e9f8877dec | ||
|
|
ed87267633 | ||
|
|
e4d469a408 | ||
|
|
5c13e8d9aa | ||
|
|
7afdd4cbf5 | ||
|
|
c155317a6b | ||
|
|
a70513985a | ||
|
|
ffc2324dd5 | ||
|
|
9ef975d467 | ||
|
|
fa9c88c7be | ||
|
|
2d6b9b775e | ||
|
|
8a48371aec | ||
|
|
c891a0fa26 | ||
|
|
99728fd237 | ||
|
|
b76c65441b | ||
|
|
4ca8ec005d | ||
|
|
db3c1b22ce | ||
|
|
b0525bf06b | ||
|
|
d884731691 | ||
|
|
a4e4d089f7 | ||
|
|
0b31ee8562 | ||
|
|
aaf8969af2 | ||
|
|
f7c34aa572 | ||
|
|
16e4c11961 | ||
|
|
116003f189 | ||
|
|
6c87de30dd | ||
|
|
60b2601d8c | ||
|
|
ca37bda739 | ||
|
|
c4698212ce | ||
|
|
0e0d4031c9 | ||
|
|
be1f885341 | ||
|
|
a6c8eedfd4 | ||
|
|
32f9600d51 | ||
|
|
03f99c88ae | ||
|
|
47882191bc | ||
|
|
d7962ae659 | ||
|
|
cbd17b0290 | ||
|
|
839325ab70 | ||
|
|
13dc4304fc | ||
|
|
c49a937fb8 | ||
|
|
36d3905d0e | ||
|
|
9ff748dfd5 | ||
|
|
40878794cf | ||
|
|
a17a69c93a | ||
|
|
469ca98b8a | ||
|
|
330cc1cd3a | ||
|
|
5c61819b25 | ||
|
|
2c31c26cb9 | ||
|
|
a4f490b607 | ||
|
|
36007330e9 | ||
|
|
fba890af81 | ||
|
|
619fee0821 | ||
|
|
4f7d3f4e4e | ||
|
|
ac1f1d2de4 | ||
|
|
5856edd962 | ||
|
|
ac463d081e | ||
|
|
dbd1a872c4 | ||
|
|
ff600d652d | ||
|
|
8a512a13a4 | ||
|
|
a9f1a25da4 | ||
|
|
0fd05f2c36 | ||
|
|
aa0754a53f | ||
|
|
d31d0aa735 | ||
|
|
c92b26928c | ||
|
|
70f5f1be30 | ||
|
|
4ae769506e | ||
|
|
73c6b67830 | ||
|
|
f05963c6ea | ||
|
|
d99b687989 | ||
|
|
d57e4bd077 | ||
|
|
e5c2a18bd6 | ||
|
|
796a2b44c8 | ||
|
|
5e67cf8292 | ||
|
|
5e4880f1d1 | ||
|
|
ea161d83fa | ||
|
|
0f28ca779d | ||
|
|
7711af1214 | ||
|
|
9817eb303b | ||
|
|
21d782b4be | ||
|
|
f14d816e6b | ||
|
|
025ee4df18 | ||
|
|
1b2870a1b2 | ||
|
|
03760521ac | ||
|
|
e4d8983280 | ||
|
|
64431e3661 | ||
|
|
ace56ece7e | ||
|
|
135724fab5 | ||
|
|
5d72b7ce98 | ||
|
|
6697a44259 | ||
|
|
85165850c1 | ||
|
|
3bd24d80c0 | ||
|
|
aab31719d4 | ||
|
|
1ffc84663a | ||
|
|
9f703cf64e | ||
|
|
44b1e2a4e5 | ||
|
|
3e7fa3e440 | ||
|
|
20154d8411 | ||
|
|
26b2a2278e | ||
|
|
886901b48e | ||
|
|
a817967aa0 | ||
|
|
52bbfdb9f4 | ||
|
|
8c74096a7f | ||
|
|
153362393b | ||
|
|
3185d0b38b | ||
|
|
0be8b711ac | ||
|
|
9e60002210 | ||
|
|
fe169f267d | ||
|
|
26c09c3993 | ||
|
|
49f516685f | ||
|
|
89ca710932 | ||
|
|
f1d01341cf | ||
|
|
c30beda09d | ||
|
|
4b4d9da7b1 | ||
|
|
32a94fbb37 | ||
|
|
a589857582 | ||
|
|
f9c20c4a91 | ||
|
|
886d07a4c3 | ||
|
|
b1ca421e52 | ||
|
|
1cb583a3ba | ||
|
|
4683318351 | ||
|
|
e17a4756e7 | ||
|
|
7622f7c550 | ||
|
|
e25332ed27 | ||
|
|
1f270a5231 | ||
|
|
cc32286389 | ||
|
|
d78b2f2fc0 | ||
|
|
a406bc94a8 | ||
|
|
765a743d5b | ||
|
|
40a0d3a3e4 | ||
|
|
b612791fc8 | ||
|
|
c4cc51cf8e | ||
|
|
3494d388bb | ||
|
|
88b887c579 | ||
|
|
061f3ce78f | ||
|
|
944d2475dd | ||
|
|
1d1e57a265 | ||
|
|
522b874f54 | ||
|
|
dea2cfad1c | ||
|
|
1c8369d6fd | ||
|
|
98d8d65687 | ||
|
|
c5c9f5a447 | ||
|
|
c42a3ac6e0 | ||
|
|
be60c4f865 | ||
|
|
fd9bb6f36a | ||
|
|
27118a0f7a | ||
|
|
665d4af5a2 | ||
|
|
1fd2aa17d5 | ||
|
|
bd8db95098 | ||
|
|
fcb3f64d9b | ||
|
|
ee36ab94b6 | ||
|
|
ac86437b35 | ||
|
|
30d8d9f3fe | ||
|
|
f2ace5cd3d | ||
|
|
cc1a71f246 | ||
|
|
b3d8eaee7d | ||
|
|
c70861d7d3 | ||
|
|
4cfa915aec | ||
|
|
2bf31add95 | ||
|
|
61526dea82 | ||
|
|
5c719c27b0 | ||
|
|
342207375e | ||
|
|
ce5b26b0d0 | ||
|
|
6056bfbc49 | ||
|
|
4e925b0385 | ||
|
|
edff4117f5 | ||
|
|
64f2d62605 | ||
|
|
1efe09eeca | ||
|
|
2375176d72 | ||
|
|
3bfc7da93a | ||
|
|
34b4e90d75 | ||
|
|
48ab353521 | ||
|
|
73f872bf6e | ||
|
|
f04c8be999 | ||
|
|
d6eb49db68 | ||
|
|
c03ada1e1c | ||
|
|
9ce3d6182b | ||
|
|
ff3f60cc68 | ||
|
|
89aa4bfab3 | ||
|
|
746e60b517 | ||
|
|
8634cc561f | ||
|
|
d384f0896a | ||
|
|
9f5cc344ad | ||
|
|
45bebdff0c | ||
|
|
59ef9e132a | ||
|
|
af8e9c2466 | ||
|
|
e11163f483 | ||
|
|
d3ff20484d | ||
|
|
76cbbb2bfe | ||
|
|
c51ad4aaa6 | ||
|
|
6d5e56f6bf | ||
|
|
251569e81c | ||
|
|
be2759c354 | ||
|
|
69ee8f3630 | ||
|
|
610991a222 | ||
|
|
f08a0eaffd | ||
|
|
a0baa659ec | ||
|
|
536a19b373 | ||
|
|
27a2d7f642 | ||
|
|
fc29cec8c8 | ||
|
|
5fe19d75a6 | ||
|
|
3252489971 | ||
|
|
f90af4784e | ||
|
|
a2f11fb829 | ||
|
|
b6e6d308a9 | ||
|
|
dd04888106 | ||
|
|
0c397a414e | ||
|
|
2a62001048 | ||
|
|
3132e5dd5b | ||
|
|
8843a12333 | ||
|
|
b2e97ac73b | ||
|
|
11c440209a | ||
|
|
400adeed1c | ||
|
|
abcdc7253e | ||
|
|
b6b8b9440f | ||
|
|
c9a64b8e96 | ||
|
|
327795855a | ||
|
|
b3c5494e74 | ||
|
|
9fc4a247aa | ||
|
|
013cd0a9a8 | ||
|
|
05a9da4eb8 | ||
|
|
81d853ac04 | ||
|
|
af8dfa685a | ||
|
|
946a360652 | ||
|
|
071fe9a910 | ||
|
|
ccb4e87002 | ||
|
|
cd9adb166a | ||
|
|
4f95f2c842 | ||
|
|
7e6771d550 | ||
|
|
0b025fd0ae | ||
|
|
9445204ffc | ||
|
|
0b69923ad7 | ||
|
|
00eb90bcc6 | ||
|
|
8478967c04 | ||
|
|
08f4b5bbe6 | ||
|
|
828a5e7f46 | ||
|
|
2e6151fe25 | ||
|
|
c5fad910ba | ||
|
|
8d7c672b94 | ||
|
|
67de9ba209 | ||
|
|
d3486341f3 | ||
|
|
ac12412ec3 | ||
|
|
73e0a4d35a | ||
|
|
1e09e9e3f8 | ||
|
|
d97265f2f2 | ||
|
|
cd55c4077e | ||
|
|
0a9d5b73d8 | ||
|
|
e5e1de83a1 | ||
|
|
359ab4233b | ||
|
|
715f991d0d | ||
|
|
e00b18bf40 | ||
|
|
77869fd273 | ||
|
|
1f4de4af80 | ||
|
|
6d34e04138 | ||
|
|
1761ccffd0 | ||
|
|
4de53b29f0 | ||
|
|
08c02b7473 | ||
|
|
a648e888c5 | ||
|
|
af94789b7f | ||
|
|
c4520a0ac2 | ||
|
|
75bec0a132 | ||
|
|
b680878558 | ||
|
|
3f95ca2869 | ||
|
|
003d7ac606 | ||
|
|
b61267d3bd | ||
|
|
a6728853db | ||
|
|
d95c46e1b8 | ||
|
|
d3a18c55d3 | ||
|
|
8bffadeb14 | ||
|
|
4abb037081 | ||
|
|
07d6cebba4 | ||
|
|
abd929070e | ||
|
|
ea937a410b | ||
|
|
64159bab6f | ||
|
|
164e6656e8 | ||
|
|
0bac699cbf | ||
|
|
2030ffcea3 | ||
|
|
6433eff8ec | ||
|
|
6c51f7c6bc | ||
|
|
0c82e0614c | ||
|
|
acd30aaf08 | ||
|
|
ce2f75d43d | ||
|
|
3f3875cc0c | ||
|
|
e939218a65 | ||
|
|
d929a8b140 | ||
|
|
3e1aef4a58 | ||
|
|
93dad0bf44 | ||
|
|
d5259e8bfb | ||
|
|
440d7fe2b8 | ||
|
|
0e666f07e9 | ||
|
|
1b542e936b | ||
|
|
13487bbeed | ||
|
|
7a71716c7f | ||
|
|
e72baa9dd4 | ||
|
|
4c9f188b0c | ||
|
|
e78f90bc62 | ||
|
|
19d5e8457f | ||
|
|
0fb0837561 | ||
|
|
6cff21f47b | ||
|
|
477408676d | ||
|
|
97b675bc9f | ||
|
|
65dab56ad0 | ||
|
|
faba97a6f5 | ||
|
|
c10a2e1133 | ||
|
|
83d13cc153 | ||
|
|
bdbdd990a7 | ||
|
|
70e4d80965 | ||
|
|
6ec8b15a34 | ||
|
|
ee21afee61 | ||
|
|
08a08855aa | ||
|
|
95ee7ba2f8 | ||
|
|
cde55f37f3 | ||
|
|
5b147d4ab2 | ||
|
|
058ef0c4c5 | ||
|
|
bb48bd22a3 | ||
|
|
0aecd7068f | ||
|
|
0d4a837be6 | ||
|
|
ac70e6504a | ||
|
|
a64f19a686 | ||
|
|
9a12f8ecab | ||
|
|
37d6e5aa3d | ||
|
|
7cbf5ff74e | ||
|
|
b6ec9728d8 | ||
|
|
b5a167df05 | ||
|
|
f01409ab2c | ||
|
|
a96778a94d | ||
|
|
e9e898da79 | ||
|
|
a8d73f6172 | ||
|
|
84a1d9997b | ||
|
|
5aa489d769 | ||
|
|
fd520f2ea8 | ||
|
|
a1a7cb747b | ||
|
|
3d79e77f47 | ||
|
|
f6c6ecd494 | ||
|
|
52cca9532b | ||
|
|
d412bcc718 | ||
|
|
6cb93e51fa | ||
|
|
84bc9205a9 | ||
|
|
92aea19abd | ||
|
|
5dc94fdf0e | ||
|
|
2089450a1d | ||
|
|
eb92fe944c | ||
|
|
3f426f63a1 | ||
|
|
8c3a96db7f | ||
|
|
edd9142f64 | ||
|
|
424b83ddeb | ||
|
|
610e844747 | ||
|
|
a6fbe153b5 | ||
|
|
0c1108b92d | ||
|
|
684b84690c | ||
|
|
df808f5690 | ||
|
|
d960623dac | ||
|
|
d30c95c58c | ||
|
|
8c1139c151 | ||
|
|
3a6adc5503 | ||
|
|
dbc1556381 | ||
|
|
bd85ed3577 | ||
|
|
1340e43f02 | ||
|
|
aee891cd7e | ||
|
|
9f39c1d305 | ||
|
|
43c23059ba | ||
|
|
12c1caf54d | ||
|
|
3848ba311e | ||
|
|
dcb6cbf5fa | ||
|
|
f70e33a3ea | ||
|
|
857d6f87a0 | ||
|
|
d3444621b9 | ||
|
|
05bfc51a20 | ||
|
|
851c593695 | ||
|
|
8ed711ad34 | ||
|
|
e8eaf60c2c | ||
|
|
df480b67c0 | ||
|
|
31e7316854 | ||
|
|
b183588046 | ||
|
|
2620769b19 | ||
|
|
9f4184974e | ||
|
|
e586debb58 | ||
|
|
eccaf83e7d | ||
|
|
a9a6227b8f | ||
|
|
f16251d046 | ||
|
|
27c508caa0 | ||
|
|
aa10845ef0 | ||
|
|
fe4c836cd2 | ||
|
|
a34afb1a81 | ||
|
|
9ee58451b5 | ||
|
|
faa8ca0b34 | ||
|
|
e084f66354 | ||
|
|
c68887f5f5 | ||
|
|
43862a4b7b | ||
|
|
036d672e7c | ||
|
|
abbbae011f | ||
|
|
bbab6a0d68 | ||
|
|
2e492dfc39 | ||
|
|
3d5606cc7b | ||
|
|
553d059113 | ||
|
|
990345cda0 | ||
|
|
1cbf075036 | ||
|
|
6e518df398 | ||
|
|
8e1af41938 | ||
|
|
da2faaeff4 | ||
|
|
66af4ef831 | ||
|
|
cc0fdb302b | ||
|
|
fd1f686469 | ||
|
|
a45a8a6011 | ||
|
|
a40c362764 | ||
|
|
b3c372a803 | ||
|
|
0ffe6051b6 | ||
|
|
3613cb704e | ||
|
|
0733f7a69a | ||
|
|
d514944657 | ||
|
|
5172aaafdb | ||
|
|
d8ec34aa6f | ||
|
|
0257acce1d | ||
|
|
f7746a0da6 | ||
|
|
52605f6a4a | ||
|
|
d865a8ba0c | ||
|
|
2869337f52 | ||
|
|
083f28c89d | ||
|
|
bd78dff68f | ||
|
|
db291eb9c4 | ||
|
|
f155244997 | ||
|
|
fa8d2f0a25 | ||
|
|
f3b0259884 | ||
|
|
258a9c046e | ||
|
|
97bda7a949 | ||
|
|
6b306860ec | ||
|
|
697c41b0d0 | ||
|
|
ee8ec490d7 | ||
|
|
c1674b568a | ||
|
|
2c7d75996e | ||
|
|
579c08a9e8 | ||
|
|
2ba6e616af | ||
|
|
5d32321aa2 | ||
|
|
ccd50fa98b | ||
|
|
69c9cb1244 | ||
|
|
35e1fca22d | ||
|
|
039dfb63ea | ||
|
|
cf53d03104 | ||
|
|
d4e72f07f9 | ||
|
|
08a6af547c | ||
|
|
dd8a526a30 | ||
|
|
298a6b48ad | ||
|
|
08d305ebfa | ||
|
|
e4f0463299 | ||
|
|
b586182160 | ||
|
|
4051da7ee1 | ||
|
|
1705cc21b4 | ||
|
|
4a3e0b244c | ||
|
|
3855fa3568 | ||
|
|
70b0e7d6c0 | ||
|
|
e40f3cb34d | ||
|
|
434deb92f8 | ||
|
|
d962905799 | ||
|
|
bb10ce78a0 | ||
|
|
0670847700 | ||
|
|
c539acb823 | ||
|
|
f233c2683e | ||
|
|
e5a7f5844c | ||
|
|
db1839c402 | ||
|
|
24b54a0937 | ||
|
|
c3f7640c4d | ||
|
|
dab74b7bba | ||
|
|
c0e9bb223e | ||
|
|
0630658d4d | ||
|
|
3094850579 | ||
|
|
7b29e2e603 | ||
|
|
53784e154d | ||
|
|
20e3bbf288 | ||
|
|
43d016c288 | ||
|
|
320cc45fc8 | ||
|
|
a1831ab338 | ||
|
|
6f57f56d1d | ||
|
|
41062dfc47 | ||
|
|
e6628d4569 | ||
|
|
7c389dbf8e | ||
|
|
651f7cc7cd | ||
|
|
bd70662314 | ||
|
|
9a45a84663 | ||
|
|
5b12d0e3f6 | ||
|
|
d9e1121796 | ||
|
|
b0f37ca217 | ||
|
|
7459f7a9fa | ||
|
|
3f3f2f2c18 | ||
|
|
42f2b0a8e3 | ||
|
|
8326dc1dc7 | ||
|
|
cc68c5cfcb | ||
|
|
9e46cc2b0b | ||
|
|
2fc827fcd7 | ||
|
|
8108bdc91b | ||
|
|
345f6fc2af | ||
|
|
af06cb5cdd | ||
|
|
ba74b333cd | ||
|
|
895d282cc8 | ||
|
|
33831ee7cc | ||
|
|
8aaea65a72 | ||
|
|
e20ba2c0a0 | ||
|
|
e276950caa | ||
|
|
a17663655b | ||
|
|
9bb5a69b56 | ||
|
|
b9afd98189 | ||
|
|
89da789a99 | ||
|
|
f78f8006d6 | ||
|
|
46c79b4213 | ||
|
|
e4fb8c20ac | ||
|
|
5a8762edb1 | ||
|
|
6285c76935 | ||
|
|
77942b632f | ||
|
|
29261c34d5 | ||
|
|
2152e5f731 | ||
|
|
b659833f71 | ||
|
|
f7d626d8c6 | ||
|
|
f1c8ace081 | ||
|
|
655015d33b | ||
|
|
ec24a92987 | ||
|
|
04237c6118 | ||
|
|
86f13e05d2 | ||
|
|
9a6146e1ba | ||
|
|
0a8ce34252 | ||
|
|
18529f47e7 | ||
|
|
98215be0fc | ||
|
|
f2698ef38d | ||
|
|
215ee66a02 | ||
|
|
e055657d13 | ||
|
|
ec7f47dbe7 | ||
|
|
e3e7e4d26a | ||
|
|
118d7d735a | ||
|
|
d7a8534354 | ||
|
|
9fe86bf72a | ||
|
|
1d76f89428 | ||
|
|
fc26af5a89 | ||
|
|
7f85a8f2e1 | ||
|
|
e3ff8029de | ||
|
|
70f5bdd339 | ||
|
|
2ecc36ab12 | ||
|
|
a4c14c6be1 | ||
|
|
6cbda005e8 | ||
|
|
8ea99648f0 | ||
|
|
58a5d9b0e1 | ||
|
|
b969f2eac6 | ||
|
|
5c295e9080 | ||
|
|
64a6c3c5bb | ||
|
|
22cc4c635a | ||
|
|
b5b96f1155 | ||
|
|
06b887cd93 | ||
|
|
c6ef376d15 | ||
|
|
6ef249195e | ||
|
|
f6e99cf748 | ||
|
|
6d39088777 | ||
|
|
e5cb026608 | ||
|
|
ce9dbc2b5f | ||
|
|
bfef677e93 | ||
|
|
0d18a1d19a | ||
|
|
91b3b7194d | ||
|
|
306d515e7f | ||
|
|
0a21e03eac | ||
|
|
e112704a63 | ||
|
|
c0bbf107c6 | ||
|
|
84a6876265 | ||
|
|
add9ffe5d9 | ||
|
|
c71f0b7e39 | ||
|
|
e934071716 | ||
|
|
5693cddc3a | ||
|
|
561d27be0a | ||
|
|
2e19a2a99c | ||
|
|
4055e421bc | ||
|
|
792255b676 | ||
|
|
0ddb8d9644 | ||
|
|
5c29555ae7 | ||
|
|
cf5e6bde9a | ||
|
|
515ddbb28d | ||
|
|
18720e3693 | ||
|
|
6b21664dff | ||
|
|
ca92f90f6d | ||
|
|
8a1ab40f3c | ||
|
|
f738822cce | ||
|
|
2ef900443b | ||
|
|
917c6b7c54 | ||
|
|
966db8af9f | ||
|
|
07b26f03a7 | ||
|
|
d685997040 | ||
|
|
fce6807a3b | ||
|
|
e91b428bd5 | ||
|
|
6581466239 | ||
|
|
380a5bbadd | ||
|
|
4da83ff5f8 | ||
|
|
d5cf8848fa | ||
|
|
4ea6f9dd6a | ||
|
|
d8027b002d | ||
|
|
623cda83d5 | ||
|
|
99b3e4493b | ||
|
|
025a201a7d | ||
|
|
3243555181 | ||
|
|
867eae442b | ||
|
|
e8e80a7ac8 | ||
|
|
480c88a73e | ||
|
|
6f34a42034 | ||
|
|
312421ed28 | ||
|
|
93e81fdc01 | ||
|
|
2d7f0f907b | ||
|
|
6aca67efa6 | ||
|
|
bab2b9f528 | ||
|
|
07886140fb | ||
|
|
90341ee27a | ||
|
|
f78da4f637 | ||
|
|
d3b8ffe328 | ||
|
|
e0b25527b3 | ||
|
|
56798a76c9 | ||
|
|
2faab75bd8 | ||
|
|
d4d7c5b35e | ||
|
|
bf07df8f7b | ||
|
|
3622103a90 | ||
|
|
31680b3849 | ||
|
|
aa7611fceb | ||
|
|
9f0a34455b | ||
|
|
da51f8af37 | ||
|
|
170853f2cc | ||
|
|
df4b625ac4 | ||
|
|
2d6ff3b74c | ||
|
|
a5b3b61052 | ||
|
|
1b1a56e77c | ||
|
|
e18dd07491 | ||
|
|
f94771730e | ||
|
|
cb24df8890 | ||
|
|
499d72d5d6 | ||
|
|
1bab2617b7 | ||
|
|
b5ed2daa81 | ||
|
|
c32cd456b6 | ||
|
|
0f9a71f07d | ||
|
|
a1711daead | ||
|
|
5b6031f41d | ||
|
|
c3787ab02f | ||
|
|
9d1a216a20 | ||
|
|
2603d08aa4 | ||
|
|
990c0afee2 | ||
|
|
1bfd401ed7 | ||
|
|
22414bab7c | ||
|
|
08222b0d5c | ||
|
|
d599d29514 | ||
|
|
e5120c7074 | ||
|
|
68f36bf172 | ||
|
|
ee025f9a13 | ||
|
|
68891d18f4 | ||
|
|
be4b7e7232 | ||
|
|
c3e03c17e8 | ||
|
|
1dc63294b4 | ||
|
|
63191515d3 | ||
|
|
f8b072f14a | ||
|
|
d09862522b | ||
|
|
ce5240223b | ||
|
|
5b8d2cb36a | ||
|
|
d9b35ca1ee | ||
|
|
e8433a1d80 | ||
|
|
092aac82ea | ||
|
|
9039178927 | ||
|
|
a8b2a627f8 | ||
|
|
da8b85decd | ||
|
|
73786bfd94 | ||
|
|
e06ea82f62 | ||
|
|
085732868c | ||
|
|
3f7c512c63 | ||
|
|
28d8b49de7 | ||
|
|
c79d04f29a | ||
|
|
d1ba022ce9 | ||
|
|
d36fb16b12 | ||
|
|
26e94d3ba6 | ||
|
|
d2cd5e7202 | ||
|
|
0bfd5cc292 | ||
|
|
deb25f8299 | ||
|
|
aab28fd3fd | ||
|
|
d9759305a3 | ||
|
|
e015e57dda | ||
|
|
44a2f1880d | ||
|
|
2e3617ad1c | ||
|
|
ae5e7d1e60 | ||
|
|
a31c3dc611 | ||
|
|
2ee3ec303d | ||
|
|
3d8a85a145 | ||
|
|
fc18153f02 | ||
|
|
77c1875b6b | ||
|
|
4388a84787 | ||
|
|
0d3a9c8b16 | ||
|
|
2ca05b5634 | ||
|
|
4569bca39d | ||
|
|
c401a609a4 | ||
|
|
07a71f1059 | ||
|
|
82f6f7d4d5 | ||
|
|
97f68a8247 | ||
|
|
1d9f867c75 | ||
|
|
ebd3c33051 | ||
|
|
00cec4faa0 | ||
|
|
690fa7bf78 | ||
|
|
ace6474157 | ||
|
|
974a017044 | ||
|
|
89917d4f75 | ||
|
|
f651364e00 | ||
|
|
2a8b43b7a3 | ||
|
|
aff991690d | ||
|
|
7f095947ab | ||
|
|
b18b2cff44 | ||
|
|
befd16238b | ||
|
|
20d3e540b0 | ||
|
|
cede2927c5 | ||
|
|
543aae0893 | ||
|
|
e9c55345b8 | ||
|
|
8c7dc08844 | ||
|
|
3ae20885c4 | ||
|
|
09042f1dbf | ||
|
|
6b5c52d2c8 | ||
|
|
e02e2c462b | ||
|
|
30a1ea7a4f | ||
|
|
51a2383574 | ||
|
|
7fbbb9b030 | ||
|
|
f2d88796ed | ||
|
|
1659ca88ca | ||
|
|
fd7ba65203 | ||
|
|
ae66502fd5 | ||
|
|
cc06fb6b40 | ||
|
|
a60c6af30e | ||
|
|
91e7cd6576 | ||
|
|
d717a68445 | ||
|
|
7f4dcf54a7 | ||
|
|
613238e0f9 | ||
|
|
72f7189aeb | ||
|
|
ea2616434c | ||
|
|
0261f25996 | ||
|
|
f5db79ce69 | ||
|
|
236a7c91d5 | ||
|
|
c6bae2116a | ||
|
|
05d42d9518 | ||
|
|
19a6236fa9 | ||
|
|
115dbc8b12 | ||
|
|
03084c3ce3 | ||
|
|
0260afa068 | ||
|
|
2f9044f53f | ||
|
|
f195e85fb8 | ||
|
|
65065343f9 | ||
|
|
50c8014977 | ||
|
|
911d0a4fae | ||
|
|
d1bb81e404 | ||
|
|
76526a0b2d | ||
|
|
a9020d92ca | ||
|
|
74be6e6c14 | ||
|
|
f203168542 | ||
|
|
3c03f5fd71 | ||
|
|
8cfc9d7793 | ||
|
|
ec5c500b8f | ||
|
|
8f7cbeb4ab | ||
|
|
fb32273477 | ||
|
|
1be49e8d8a | ||
|
|
b7edc01952 | ||
|
|
0470878ddf | ||
|
|
d07a65d553 | ||
|
|
58218f10da | ||
|
|
690eebb6a1 | ||
|
|
9bd9936781 | ||
|
|
bf62325329 | ||
|
|
a3775e0b57 | ||
|
|
03c0794b48 | ||
|
|
b5edcaf5ac | ||
|
|
3723a4e656 | ||
|
|
b332038722 | ||
|
|
33df8dd5eb | ||
|
|
7e982b327b | ||
|
|
91585464bd | ||
|
|
3c3f494034 | ||
|
|
87cbc6229a | ||
|
|
bd10364d27 | ||
|
|
36721dde56 | ||
|
|
6a74bd841a | ||
|
|
093b5451c3 | ||
|
|
896dff3fdc | ||
|
|
e8e23603c3 | ||
|
|
426fe0fe40 | ||
|
|
3e2ef43a0a | ||
|
|
a73b480b1f | ||
|
|
4d374dd587 | ||
|
|
3b97f8c396 | ||
|
|
e7a559327c | ||
|
|
ede35abe1a | ||
|
|
edc36f915d | ||
|
|
05a9d457aa | ||
|
|
cc7ce6bae9 | ||
|
|
57e5135346 | ||
|
|
74980f84d5 | ||
|
|
fa7f1cc21a | ||
|
|
8c90fb3c15 | ||
|
|
021a48c070 | ||
|
|
8be583083e | ||
|
|
8d15a7249a | ||
|
|
6259793f39 | ||
|
|
772faf8cd2 | ||
|
|
dd7141aeaa | ||
|
|
521a3f9341 | ||
|
|
452bdd7b4f | ||
|
|
37ebb5731d | ||
|
|
933ce64cce | ||
|
|
b8d0019ece | ||
|
|
4df3da0856 | ||
|
|
59f482e52e | ||
|
|
538ea82899 | ||
|
|
0b7fc67c3f | ||
|
|
7a92712f0b | ||
|
|
6ed9a8b336 | ||
|
|
24df4e78c7 | ||
|
|
a0abaf62b4 | ||
|
|
85ece1ec63 | ||
|
|
ba284aafe3 | ||
|
|
f25ab2f892 | ||
|
|
b026111ef8 | ||
|
|
9850f2e242 | ||
|
|
9313fa9193 | ||
|
|
f19c9183f0 | ||
|
|
5af3384b02 | ||
|
|
46a5512209 | ||
|
|
63c83e97c4 | ||
|
|
cf311fdeb1 | ||
|
|
04ef82bc70 | ||
|
|
7182ad2e9c | ||
|
|
072508e1c1 | ||
|
|
91101a7c40 | ||
|
|
d34fa71797 | ||
|
|
bee7bccc12 | ||
|
|
74eea096b1 | ||
|
|
02c028584b | ||
|
|
2a87d338b4 | ||
|
|
8759532964 | ||
|
|
f332db93ad | ||
|
|
6ffc687174 | ||
|
|
ba6079164c | ||
|
|
372ee945de | ||
|
|
0f0eed3de6 | ||
|
|
9eddb37c3e | ||
|
|
6a262ea604 | ||
|
|
94693f530e | ||
|
|
ee9eced602 | ||
|
|
58fa9b8182 | ||
|
|
89b377ab8f | ||
|
|
1d93fccbb3 | ||
|
|
f8786bcd47 | ||
|
|
8adf2043f7 | ||
|
|
8af3fe3db8 | ||
|
|
09c1db9afe | ||
|
|
2f738ff2b7 | ||
|
|
4429e36c3f | ||
|
|
0b8a3cfcea | ||
|
|
c3d43f6021 | ||
|
|
2a5badbc7c | ||
|
|
3c5f24f99d | ||
|
|
105212a667 | ||
|
|
f5decd2221 | ||
|
|
48bd65cd3b | ||
|
|
90046b11af | ||
|
|
6eede614f8 | ||
|
|
8cd9c5e8a3 | ||
|
|
0ef1019d1c | ||
|
|
f2a55e525b | ||
|
|
0f9eecd821 | ||
|
|
a45848cbe3 | ||
|
|
07af7d7f97 | ||
|
|
87186ca940 | ||
|
|
990bd7e291 | ||
|
|
c39aa35d36 | ||
|
|
3b187d75bb | ||
|
|
84694773a2 | ||
|
|
e8d9083f05 | ||
|
|
25ccebe617 | ||
|
|
c6d3d07b3c | ||
|
|
c6078f9314 | ||
|
|
db5462b79f | ||
|
|
3b8e6a2c3a | ||
|
|
eae7e79ecf | ||
|
|
5264873645 | ||
|
|
6009f097cd | ||
|
|
96265e5ac1 | ||
|
|
5ee33a35fc | ||
|
|
55f3c484f2 | ||
|
|
0bd678318c | ||
|
|
89ab049391 | ||
|
|
aa6e4c5b53 | ||
|
|
ec8a75be52 | ||
|
|
3ae7fe7f04 | ||
|
|
3e4b5830be | ||
|
|
8ea28dad68 | ||
|
|
17c1fc79e1 | ||
|
|
801eaaaf37 | ||
|
|
bfd72d7540 | ||
|
|
1341ef0c3e | ||
|
|
95bda0d4bb | ||
|
|
adf412ea74 | ||
|
|
a00a6734b3 | ||
|
|
e410eab336 | ||
|
|
1367357e68 | ||
|
|
4cb4b3bb19 | ||
|
|
ce92ab77ec | ||
|
|
e365fc60bc | ||
|
|
edb1343676 | ||
|
|
f1426103ff | ||
|
|
7a767327c0 | ||
|
|
e2f9139aa9 | ||
|
|
a80b504379 | ||
|
|
d73b3c44e3 | ||
|
|
c05f54c105 | ||
|
|
c917bafde5 | ||
|
|
b349106f08 | ||
|
|
1c91f345fc | ||
|
|
2b11afbb2d | ||
|
|
e9479dd24f | ||
|
|
d676412291 | ||
|
|
5fd5d032ef | ||
|
|
66ee09f656 | ||
|
|
8f57a55286 | ||
|
|
6498250114 | ||
|
|
bf7b9a7057 | ||
|
|
8de7099c0f | ||
|
|
c2b3c4919d | ||
|
|
90b5531041 | ||
|
|
e57de3268f | ||
|
|
b9e68834de | ||
|
|
5352514c2b | ||
|
|
aa58b3df35 | ||
|
|
dfe5a90b06 | ||
|
|
893486eb93 | ||
|
|
bdc684d2d1 | ||
|
|
80bc435df6 | ||
|
|
00cbfa45d7 | ||
|
|
5c18270826 | ||
|
|
aa86ef631b | ||
|
|
c67a2bd458 | ||
|
|
f796ec32a7 | ||
|
|
9ce43ea7a6 | ||
|
|
19702794e4 | ||
|
|
9015bfcac3 | ||
|
|
ad13ef9187 | ||
|
|
4a1b147a25 | ||
|
|
5970908a3f | ||
|
|
e3d37ca831 | ||
|
|
4c9afb8c80 | ||
|
|
253049ec5a | ||
|
|
c1d7f86542 | ||
|
|
aa44fba1e5 | ||
|
|
5dbf2b0ff5 | ||
|
|
8fcbaef064 | ||
|
|
08c3509823 | ||
|
|
d9961bc1a0 | ||
|
|
9fd8c1f546 | ||
|
|
d265102f31 | ||
|
|
063a50b14a | ||
|
|
ebb06b34b0 | ||
|
|
bbc3a75e4a | ||
|
|
86c5ef363e | ||
|
|
daefcab8f5 | ||
|
|
3d5f9beac5 | ||
|
|
0b01855bf4 | ||
|
|
cc78904582 | ||
|
|
306b4652eb | ||
|
|
721a629ba8 | ||
|
|
4f00aeeed7 | ||
|
|
88ca671965 | ||
|
|
7c3cfe1326 | ||
|
|
735f1007cf | ||
|
|
66df063e2a | ||
|
|
e7547905a3 | ||
|
|
206b01376d | ||
|
|
e50ef893e8 | ||
|
|
496cc470e2 | ||
|
|
b82516ba4d | ||
|
|
bbe7ff3c9b | ||
|
|
d33770f107 | ||
|
|
6523500d09 | ||
|
|
1a7a5fe6a8 | ||
|
|
9426c531e3 | ||
|
|
35a6977df8 | ||
|
|
c06280a1ea | ||
|
|
8b3ff137d1 | ||
|
|
0fa826f926 | ||
|
|
5c7b40248c | ||
|
|
7a3a8b0490 | ||
|
|
78d13749d5 | ||
|
|
1143a43d30 | ||
|
|
3cdf553142 | ||
|
|
f9569895b4 | ||
|
|
905b469515 | ||
|
|
aac3443b78 | ||
|
|
bd9b05463e | ||
|
|
bb82c6fa02 | ||
|
|
7232f9446a | ||
|
|
639f2b8e02 | ||
|
|
7d860542b1 | ||
|
|
7593dad6c8 | ||
|
|
ecc4834110 | ||
|
|
2e1adc6f9b | ||
|
|
18a0866735 | ||
|
|
4036fc44e3 | ||
|
|
9f7bbd6efc | ||
|
|
1ff11a09ed | ||
|
|
be3d4723ef | ||
|
|
190703d74b | ||
|
|
eac361d892 | ||
|
|
a772cf9910 | ||
|
|
77eab9e6f8 | ||
|
|
c4fc168369 | ||
|
|
41409fb5c1 | ||
|
|
71eb88eb7b | ||
|
|
dcbde94d78 | ||
|
|
11bbcf5b42 | ||
|
|
badf68b80a | ||
|
|
01e1c2ab0a | ||
|
|
bef51732e0 | ||
|
|
77046cf38a | ||
|
|
2a623957c3 | ||
|
|
65322925a4 | ||
|
|
346c4db3d9 | ||
|
|
7212698863 | ||
|
|
1779544d70 | ||
|
|
0270420da8 | ||
|
|
a1461851ee | ||
|
|
eaabad923e | ||
|
|
a9e12f5aca | ||
|
|
0637d5511d | ||
|
|
27682b2d07 | ||
|
|
0982852563 | ||
|
|
8147a315c2 | ||
|
|
fdf787e9b4 | ||
|
|
01d2338388 | ||
|
|
1dfec40f49 | ||
|
|
a82602faae | ||
|
|
c2aac371ae | ||
|
|
9aa6e0d39a | ||
|
|
3c177c4ebf | ||
|
|
d8b090bae2 | ||
|
|
928792bea7 | ||
|
|
99775194fd | ||
|
|
beb73828e0 | ||
|
|
87dc72b874 | ||
|
|
528ae68282 | ||
|
|
a44031cd39 | ||
|
|
af922d5171 | ||
|
|
bd6599a309 | ||
|
|
2e062b1ad6 | ||
|
|
5fc6654045 | ||
|
|
32c3494098 | ||
|
|
379461820b | ||
|
|
424cb7a9f2 | ||
|
|
b41e2b6f51 | ||
|
|
84e73f6f52 | ||
|
|
39fb42db10 | ||
|
|
85c18ce07b | ||
|
|
3fe548d8b7 | ||
|
|
eb32222d39 | ||
|
|
e70d7ab5c1 | ||
|
|
a3da735087 | ||
|
|
8c12b5a47e | ||
|
|
65c27c6c93 | ||
|
|
4c31e1a4b5 | ||
|
|
7be776d417 | ||
|
|
1a93e8f331 | ||
|
|
b6107f79dd | ||
|
|
fb0acb6a8b | ||
|
|
240a898fe5 | ||
|
|
3aad5916f5 | ||
|
|
c2a7f19d38 | ||
|
|
0a5fe2e2af | ||
|
|
f6e5256ae4 | ||
|
|
46aefe6b8a | ||
|
|
af7ef111b4 | ||
|
|
c918743187 | ||
|
|
817a691248 | ||
|
|
04e33448e0 | ||
|
|
aa4624e344 | ||
|
|
1b7a819a2b | ||
|
|
096036f2a4 | ||
|
|
b488ff053a | ||
|
|
04f24faf32 | ||
|
|
c180343853 | ||
|
|
da0739ab21 | ||
|
|
aa1d2a1879 | ||
|
|
9d9ef0069a | ||
|
|
50790b94a3 | ||
|
|
59b7f9c775 | ||
|
|
0f83f23052 | ||
|
|
1da95433ae | ||
|
|
d0579b5f75 | ||
|
|
1d19f56de8 | ||
|
|
3688e13137 | ||
|
|
45d71d0c5f | ||
|
|
d8be84b617 | ||
|
|
f7c4fe8e91 | ||
|
|
9005598f90 | ||
|
|
5e508770ef | ||
|
|
8331f92775 | ||
|
|
4c3c7e3807 | ||
|
|
1e39e3e815 | ||
|
|
5f6519d4c6 | ||
|
|
e93c35bcae | ||
|
|
a9d522d1d9 | ||
|
|
44ac42a4b5 | ||
|
|
78a60c750a | ||
|
|
6012b06a38 | ||
|
|
3ff370a903 | ||
|
|
6147d64a74 | ||
|
|
91b839dd26 | ||
|
|
cdb6d10519 | ||
|
|
586abb5466 | ||
|
|
710ac14202 | ||
|
|
a8735d1b4c | ||
|
|
f9ede752f2 | ||
|
|
5bdb205168 | ||
|
|
b606b939a6 | ||
|
|
0e0b12dcc0 | ||
|
|
b9e7b1f29b | ||
|
|
f930f2aa80 | ||
|
|
8698f7e4ab | ||
|
|
40b62c09f7 | ||
|
|
8ddc3ae172 | ||
|
|
b3e2fb4046 | ||
|
|
42558f2bd4 | ||
|
|
1e23b0a17e | ||
|
|
5b6e0d5c10 | ||
|
|
a53de75873 | ||
|
|
b3dc4671d0 | ||
|
|
ef4dd57032 | ||
|
|
ad1e17f329 | ||
|
|
2ba53e2dab | ||
|
|
909b8e7127 | ||
|
|
8280401e10 | ||
|
|
6096fc1c92 | ||
|
|
0378d54acc | ||
|
|
d33b642b8e | ||
|
|
27d68bcf45 | ||
|
|
b0122a8e38 | ||
|
|
96cbb63c84 | ||
|
|
bd16899c56 | ||
|
|
b874a2b76d | ||
|
|
aaf16273a9 | ||
|
|
f83e03e775 | ||
|
|
5328e443db | ||
|
|
ed49943039 | ||
|
|
220fdcdc75 | ||
|
|
a1b2a5149d | ||
|
|
3090a991a6 | ||
|
|
e0cae27f2f | ||
|
|
21c7c74b31 | ||
|
|
441693ac36 | ||
|
|
77910d82fc | ||
|
|
05258a5b48 | ||
|
|
387abd1606 | ||
|
|
3e4efc22a6 | ||
|
|
2851e51af8 | ||
|
|
ad8ad35c03 | ||
|
|
20d27bb9b3 | ||
|
|
30e878cbf9 | ||
|
|
374f6fcc1b | ||
|
|
4ea7d68bff | ||
|
|
88491f61e2 | ||
|
|
abe710f3ab | ||
|
|
3e4e87db23 | ||
|
|
e7ca1a13a6 | ||
|
|
aca78410f6 | ||
|
|
989331cbb4 | ||
|
|
7a8267a772 | ||
|
|
37ca383360 | ||
|
|
90eb55fbf8 | ||
|
|
e96c503b11 | ||
|
|
99ce6cf29d | ||
|
|
e14952607d | ||
|
|
de3efa0fe7 | ||
|
|
9af123523a | ||
|
|
7bc5adbde2 | ||
|
|
5287978e2f | ||
|
|
d364cc0f51 | ||
|
|
e7c30da23c | ||
|
|
1e6467cf80 | ||
|
|
12452c9b2a | ||
|
|
92b985d24d | ||
|
|
a0f640c739 | ||
|
|
46ecbc9c42 | ||
|
|
96f97e41e1 | ||
|
|
5726f05531 | ||
|
|
b389209c05 | ||
|
|
a82b3fd88a | ||
|
|
04a125d20f | ||
|
|
fc2e93a57f | ||
|
|
cfe9c17b58 | ||
|
|
b24f31f98c | ||
|
|
5263edc3e2 | ||
|
|
99b80cccad | ||
|
|
9794c5a188 | ||
|
|
2bd19736f4 | ||
|
|
13376d0ced | ||
|
|
1aa8475295 | ||
|
|
4bffe22f68 | ||
|
|
094941ab8c | ||
|
|
1a1bcc8d5c | ||
|
|
e77eb8a563 | ||
|
|
c063ccfa02 | ||
|
|
b1ccb5c3de | ||
|
|
29b4704c4b | ||
|
|
931d7d55dd | ||
|
|
2df0403c94 | ||
|
|
9cd878bad1 | ||
|
|
4c816eabc1 | ||
|
|
d28cbf3863 | ||
|
|
6269719b70 | ||
|
|
8a347e71ed | ||
|
|
9c44662f2e | ||
|
|
884291a8a3 | ||
|
|
97428909a4 | ||
|
|
a0bb52ff5f | ||
|
|
1a766c3ccc | ||
|
|
19bea70dc0 | ||
|
|
322a7020e3 | ||
|
|
242444ecc9 | ||
|
|
4820ebba84 | ||
|
|
7658481db2 | ||
|
|
f41644bc97 | ||
|
|
2163eb5910 | ||
|
|
ffbaf1e54f | ||
|
|
76ac47bbc6 | ||
|
|
d499f1af65 | ||
|
|
a249eeab22 | ||
|
|
712e7dbeb8 | ||
|
|
22571acd0c | ||
|
|
eba8d0bd80 | ||
|
|
4b52dea65a | ||
|
|
a5eeaae0d0 | ||
|
|
0d85eb6b90 | ||
|
|
3abb226b5c | ||
|
|
d38fdfb291 | ||
|
|
a63361302d | ||
|
|
069dbce000 | ||
|
|
5970c575c8 | ||
|
|
6ba659ff55 | ||
|
|
2c9f0e3c7d | ||
|
|
06359b9d06 | ||
|
|
7f31fd5092 | ||
|
|
01cdd82cc5 | ||
|
|
c6094cf3ee | ||
|
|
2ba7da0486 | ||
|
|
dcc0295b4b | ||
|
|
8632b0ffec | ||
|
|
bc0b69857a | ||
|
|
198d48fd84 | ||
|
|
e85d6bebd0 | ||
|
|
0679d9a30f | ||
|
|
b0584d4499 | ||
|
|
51bfc37a27 | ||
|
|
c979b27653 | ||
|
|
184f0fad99 | ||
|
|
f9b88c2738 | ||
|
|
f47a679dda | ||
|
|
e5e56b5260 | ||
|
|
c62f6b41d2 | ||
|
|
6b32c4997d | ||
|
|
cb89c96dac | ||
|
|
b31ca263fd | ||
|
|
a0451cfab0 | ||
|
|
4baecd9f79 | ||
|
|
381851aba7 | ||
|
|
23d3d5973b | ||
|
|
fcdf9cf2ec | ||
|
|
9831236d79 | ||
|
|
c644d6bf2f | ||
|
|
d82b008c33 | ||
|
|
d53f9e99af | ||
|
|
870b168cea | ||
|
|
094e2478f9 | ||
|
|
110b9f5058 | ||
|
|
d959753641 | ||
|
|
027ecf29ae | ||
|
|
d4392635bf | ||
|
|
c72370eff0 | ||
|
|
6b799be7dd | ||
|
|
244d5bba47 | ||
|
|
d539ddf018 | ||
|
|
f279792273 | ||
|
|
aa4a793566 | ||
|
|
1aaa2340f8 | ||
|
|
329247391b | ||
|
|
3fc5daef01 | ||
|
|
e087d04c17 | ||
|
|
a3a713b608 | ||
|
|
98251a8631 | ||
|
|
b147a16487 | ||
|
|
411b86197a | ||
|
|
8acab6b662 | ||
|
|
dba550550c | ||
|
|
74cfc733dd | ||
|
|
23981accd1 | ||
|
|
209f34124e | ||
|
|
14cff4facf | ||
|
|
dac089b0fb | ||
|
|
52aeda7faf | ||
|
|
5273f5c9b6 | ||
|
|
d2a30ea049 | ||
|
|
716ba3eea8 | ||
|
|
5cece53e50 | ||
|
|
ba73f8a323 | ||
|
|
81379a49a7 | ||
|
|
77e4862b3e | ||
|
|
1a3bd2aa74 | ||
|
|
9997b4a2de | ||
|
|
74b12606ef | ||
|
|
14d5034b8e | ||
|
|
8b282818e9 | ||
|
|
48a4f8ba67 | ||
|
|
1dd048eed4 | ||
|
|
e398d6af75 | ||
|
|
39b1c1f7e4 | ||
|
|
ff78b384ac | ||
|
|
d45a6f94f5 | ||
|
|
48fe8d5762 | ||
|
|
36b493f03a | ||
|
|
670ccaf891 | ||
|
|
d16a48a1a2 | ||
|
|
3f4eedbeab | ||
|
|
ef3dd4a833 | ||
|
|
fee074db42 | ||
|
|
5463af7f60 | ||
|
|
b776cd4a91 | ||
|
|
45945e839f | ||
|
|
043ad7e3aa | ||
|
|
d04e186dea | ||
|
|
4253365e03 | ||
|
|
07437b7880 | ||
|
|
5ddd007f67 | ||
|
|
814978a178 | ||
|
|
5350e77125 | ||
|
|
22e2e32377 | ||
|
|
58b2c36498 | ||
|
|
0e4af7dbcf | ||
|
|
3aba1c5ccf | ||
|
|
44af80f336 | ||
|
|
4591f5b372 | ||
|
|
2270b2a224 | ||
|
|
4eb68aae77 | ||
|
|
c92e3cca6b | ||
|
|
13ba0121d7 | ||
|
|
be4943a9d6 | ||
|
|
9484e3b52d | ||
|
|
ef731703e8 | ||
|
|
11b86806de | ||
|
|
b872f59a01 | ||
|
|
b2fc1cc1f2 | ||
|
|
6907c9c550 | ||
|
|
93701585f8 | ||
|
|
50e596e63a | ||
|
|
51ece30c34 | ||
|
|
110a078074 | ||
|
|
85b222d9e2 | ||
|
|
c974d4f30a | ||
|
|
75916a21a6 | ||
|
|
8e2fc40105 | ||
|
|
f81bd857ed | ||
|
|
e7ac05db75 | ||
|
|
eb3f526a96 | ||
|
|
371b0bdce1 | ||
|
|
06e70e7476 | ||
|
|
a748fe6610 | ||
|
|
f43dd4d3dc | ||
|
|
9c01b18922 | ||
|
|
932fd032e8 | ||
|
|
c392deaf38 | ||
|
|
c575c1bea3 | ||
|
|
38ec5efa32 | ||
|
|
70764e73c3 | ||
|
|
03fd7fba79 | ||
|
|
33d56a287b | ||
|
|
c3523f93aa | ||
|
|
26f08c7a30 | ||
|
|
a3266cda45 | ||
|
|
80052fabe8 | ||
|
|
3819a845f0 | ||
|
|
d66c0f2d61 | ||
|
|
0bf9da71ab | ||
|
|
a07ff51c68 | ||
|
|
48faabee49 | ||
|
|
d2f9eeab12 | ||
|
|
c8ba4f0379 | ||
|
|
fba0fe1866 | ||
|
|
9685b92bc9 | ||
|
|
3ae9779eec | ||
|
|
ebbdc46305 | ||
|
|
4fb1c866da | ||
|
|
431d5ef8a8 | ||
|
|
174d1f7838 | ||
|
|
b57ecea305 | ||
|
|
64f39dcb79 | ||
|
|
86149145f6 | ||
|
|
f3c41e9a66 | ||
|
|
2a6723f8db | ||
|
|
2bb8d8b52a | ||
|
|
ac86cf19a0 | ||
|
|
ae29fef6d4 | ||
|
|
54486cb5be | ||
|
|
22e41df8ea | ||
|
|
d6dfc2ffb6 | ||
|
|
cb9d0ce3ed | ||
|
|
cef5117e29 | ||
|
|
466af94499 | ||
|
|
ab9dea6930 | ||
|
|
cc2c585a55 | ||
|
|
f9b1a59368 | ||
|
|
23cbe917c2 | ||
|
|
971f9694da | ||
|
|
32f057fa26 | ||
|
|
8c7edaaca4 | ||
|
|
205323200a | ||
|
|
9b880eb669 | ||
|
|
3ce53cedc8 | ||
|
|
37e44968dc | ||
|
|
4fb58bc005 | ||
|
|
b24832dd84 | ||
|
|
57aa32f0c1 | ||
|
|
60c20cc628 | ||
|
|
eaae999878 | ||
|
|
f0af71d8c0 | ||
|
|
b65fed7a0d | ||
|
|
364205cd9b | ||
|
|
081ea769a1 | ||
|
|
7fef69635f | ||
|
|
e3c3315873 | ||
|
|
bb0218d3c6 | ||
|
|
4e3e721c5a | ||
|
|
79321a0ffb | ||
|
|
230644141a | ||
|
|
480fa113b7 | ||
|
|
9016d937a3 | ||
|
|
a361576f8a | ||
|
|
bee695afd4 | ||
|
|
f3fcdf808f | ||
|
|
8e469493ac | ||
|
|
872df9ee70 | ||
|
|
69806f1260 | ||
|
|
0a24feab13 | ||
|
|
e40766583e | ||
|
|
fd3fb5041b | ||
|
|
8ffcaf328e | ||
|
|
5381e54469 | ||
|
|
f571685648 | ||
|
|
701d363e3c | ||
|
|
afa697031c | ||
|
|
7239c98a4c | ||
|
|
48c0297fe7 | ||
|
|
a02be14f1a | ||
|
|
a0b8c758b4 | ||
|
|
97a8029f74 | ||
|
|
4d6781b26b | ||
|
|
b2d24725de | ||
|
|
b04ac67860 | ||
|
|
61784fd48d | ||
|
|
f29738a96b | ||
|
|
cb313ed513 | ||
|
|
e23656a2cd | ||
|
|
8a39ff469f | ||
|
|
89c369189a | ||
|
|
1108c92beb | ||
|
|
e43cfce016 | ||
|
|
5931b53896 | ||
|
|
020b31cb6f | ||
|
|
8fc41c4c45 | ||
|
|
d9b3c2c15c | ||
|
|
10a9825f82 | ||
|
|
2bbe168200 | ||
|
|
d6e8a96b19 | ||
|
|
c34fae302e | ||
|
|
7957135979 | ||
|
|
c9330d0b7f | ||
|
|
9df5fc12cd | ||
|
|
dd64685049 | ||
|
|
eb54b90001 | ||
|
|
b1ba5790cc | ||
|
|
56d79f36dc | ||
|
|
bbcf7b638f | ||
|
|
94cc314738 | ||
|
|
39eb6482e3 | ||
|
|
85e66ebd8a | ||
|
|
322b7084ea | ||
|
|
8ac2c0462a | ||
|
|
24e2e29894 | ||
|
|
e00aeb39e5 | ||
|
|
6ec2849bd5 | ||
|
|
698c9d2923 | ||
|
|
82f379ecbe | ||
|
|
a502ceea53 | ||
|
|
53e4b39066 | ||
|
|
fceea79323 | ||
|
|
6adf49de45 | ||
|
|
0b85346ac4 | ||
|
|
53f0544da8 | ||
|
|
839ced1b32 | ||
|
|
b6fcbba57d | ||
|
|
b7f220d02b | ||
|
|
398ae8946c | ||
|
|
65357e1441 | ||
|
|
0a7f579bd8 | ||
|
|
2e0d2bb5e5 | ||
|
|
0612d447dc | ||
|
|
fd7e19d339 | ||
|
|
7f8dec161d | ||
|
|
26f50d109a | ||
|
|
acfdf23c22 | ||
|
|
a15588120b | ||
|
|
da28816967 | ||
|
|
53bf9922ca | ||
|
|
363ff9610d | ||
|
|
772c9bc77d | ||
|
|
c1f10c32c0 | ||
|
|
0a8c36e086 | ||
|
|
ae35e4b589 | ||
|
|
92a760541b | ||
|
|
3c67546f4a | ||
|
|
695670cc96 | ||
|
|
0f42aa4ec7 | ||
|
|
8aca69f845 | ||
|
|
63be38a3c3 | ||
|
|
e7f6e5c740 | ||
|
|
2ae8d49ec8 | ||
|
|
f9e987e933 | ||
|
|
8e9c76a9e8 | ||
|
|
ad52b60798 | ||
|
|
012cc28f8a | ||
|
|
1668717cf8 |
254
.fbprefs
254
.fbprefs
@@ -1,127 +1,127 @@
|
|||||||
#FindBugs User Preferences
|
#FindBugs User Preferences
|
||||||
#Wed Jul 27 18:41:56 EDT 2011
|
#Wed Jul 27 18:41:56 EDT 2011
|
||||||
detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true
|
detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true
|
||||||
detectorBadAppletConstructor=BadAppletConstructor|false
|
detectorBadAppletConstructor=BadAppletConstructor|false
|
||||||
detectorBadResultSetAccess=BadResultSetAccess|true
|
detectorBadResultSetAccess=BadResultSetAccess|true
|
||||||
detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true
|
detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true
|
||||||
detectorBadUseOfReturnValue=BadUseOfReturnValue|true
|
detectorBadUseOfReturnValue=BadUseOfReturnValue|true
|
||||||
detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true
|
detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true
|
||||||
detectorBooleanReturnNull=BooleanReturnNull|true
|
detectorBooleanReturnNull=BooleanReturnNull|true
|
||||||
detectorCallToUnsupportedMethod=CallToUnsupportedMethod|false
|
detectorCallToUnsupportedMethod=CallToUnsupportedMethod|false
|
||||||
detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true
|
detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true
|
||||||
detectorCheckTypeQualifiers=CheckTypeQualifiers|true
|
detectorCheckTypeQualifiers=CheckTypeQualifiers|true
|
||||||
detectorCloneIdiom=CloneIdiom|true
|
detectorCloneIdiom=CloneIdiom|true
|
||||||
detectorComparatorIdiom=ComparatorIdiom|true
|
detectorComparatorIdiom=ComparatorIdiom|true
|
||||||
detectorConfusedInheritance=ConfusedInheritance|true
|
detectorConfusedInheritance=ConfusedInheritance|true
|
||||||
detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true
|
detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true
|
||||||
detectorCrossSiteScripting=CrossSiteScripting|true
|
detectorCrossSiteScripting=CrossSiteScripting|true
|
||||||
detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true
|
detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true
|
||||||
detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true
|
detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true
|
||||||
detectorDontIgnoreResultOfPutIfAbsent=DontIgnoreResultOfPutIfAbsent|true
|
detectorDontIgnoreResultOfPutIfAbsent=DontIgnoreResultOfPutIfAbsent|true
|
||||||
detectorDontUseEnum=DontUseEnum|true
|
detectorDontUseEnum=DontUseEnum|true
|
||||||
detectorDroppedException=DroppedException|true
|
detectorDroppedException=DroppedException|true
|
||||||
detectorDumbMethodInvocations=DumbMethodInvocations|true
|
detectorDumbMethodInvocations=DumbMethodInvocations|true
|
||||||
detectorDumbMethods=DumbMethods|true
|
detectorDumbMethods=DumbMethods|true
|
||||||
detectorDuplicateBranches=DuplicateBranches|true
|
detectorDuplicateBranches=DuplicateBranches|true
|
||||||
detectorEmptyZipFileEntry=EmptyZipFileEntry|true
|
detectorEmptyZipFileEntry=EmptyZipFileEntry|true
|
||||||
detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true
|
detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true
|
||||||
detectorFinalizerNullsFields=FinalizerNullsFields|true
|
detectorFinalizerNullsFields=FinalizerNullsFields|true
|
||||||
detectorFindBadCast2=FindBadCast2|true
|
detectorFindBadCast2=FindBadCast2|true
|
||||||
detectorFindBadForLoop=FindBadForLoop|true
|
detectorFindBadForLoop=FindBadForLoop|true
|
||||||
detectorFindCircularDependencies=FindCircularDependencies|false
|
detectorFindCircularDependencies=FindCircularDependencies|false
|
||||||
detectorFindDeadLocalStores=FindDeadLocalStores|true
|
detectorFindDeadLocalStores=FindDeadLocalStores|true
|
||||||
detectorFindDoubleCheck=FindDoubleCheck|true
|
detectorFindDoubleCheck=FindDoubleCheck|true
|
||||||
detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true
|
detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true
|
||||||
detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true
|
detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true
|
||||||
detectorFindFinalizeInvocations=FindFinalizeInvocations|true
|
detectorFindFinalizeInvocations=FindFinalizeInvocations|true
|
||||||
detectorFindFloatEquality=FindFloatEquality|true
|
detectorFindFloatEquality=FindFloatEquality|true
|
||||||
detectorFindHEmismatch=FindHEmismatch|true
|
detectorFindHEmismatch=FindHEmismatch|true
|
||||||
detectorFindInconsistentSync2=FindInconsistentSync2|true
|
detectorFindInconsistentSync2=FindInconsistentSync2|true
|
||||||
detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true
|
detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true
|
||||||
detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true
|
detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true
|
||||||
detectorFindMaskedFields=FindMaskedFields|true
|
detectorFindMaskedFields=FindMaskedFields|true
|
||||||
detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true
|
detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true
|
||||||
detectorFindNakedNotify=FindNakedNotify|true
|
detectorFindNakedNotify=FindNakedNotify|true
|
||||||
detectorFindNonSerializableStoreIntoSession=FindNonSerializableStoreIntoSession|true
|
detectorFindNonSerializableStoreIntoSession=FindNonSerializableStoreIntoSession|true
|
||||||
detectorFindNonSerializableValuePassedToWriteObject=FindNonSerializableValuePassedToWriteObject|true
|
detectorFindNonSerializableValuePassedToWriteObject=FindNonSerializableValuePassedToWriteObject|true
|
||||||
detectorFindNonShortCircuit=FindNonShortCircuit|true
|
detectorFindNonShortCircuit=FindNonShortCircuit|true
|
||||||
detectorFindNullDeref=FindNullDeref|true
|
detectorFindNullDeref=FindNullDeref|true
|
||||||
detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true
|
detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true
|
||||||
detectorFindOpenStream=FindOpenStream|true
|
detectorFindOpenStream=FindOpenStream|true
|
||||||
detectorFindPuzzlers=FindPuzzlers|true
|
detectorFindPuzzlers=FindPuzzlers|true
|
||||||
detectorFindRefComparison=FindRefComparison|true
|
detectorFindRefComparison=FindRefComparison|true
|
||||||
detectorFindReturnRef=FindReturnRef|true
|
detectorFindReturnRef=FindReturnRef|true
|
||||||
detectorFindRunInvocations=FindRunInvocations|true
|
detectorFindRunInvocations=FindRunInvocations|true
|
||||||
detectorFindSelfComparison=FindSelfComparison|true
|
detectorFindSelfComparison=FindSelfComparison|true
|
||||||
detectorFindSelfComparison2=FindSelfComparison2|true
|
detectorFindSelfComparison2=FindSelfComparison2|true
|
||||||
detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true
|
detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true
|
||||||
detectorFindSpinLoop=FindSpinLoop|true
|
detectorFindSpinLoop=FindSpinLoop|true
|
||||||
detectorFindSqlInjection=FindSqlInjection|true
|
detectorFindSqlInjection=FindSqlInjection|true
|
||||||
detectorFindTwoLockWait=FindTwoLockWait|true
|
detectorFindTwoLockWait=FindTwoLockWait|true
|
||||||
detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true
|
detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true
|
||||||
detectorFindUnconditionalWait=FindUnconditionalWait|true
|
detectorFindUnconditionalWait=FindUnconditionalWait|true
|
||||||
detectorFindUninitializedGet=FindUninitializedGet|true
|
detectorFindUninitializedGet=FindUninitializedGet|true
|
||||||
detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true
|
detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true
|
||||||
detectorFindUnreleasedLock=FindUnreleasedLock|true
|
detectorFindUnreleasedLock=FindUnreleasedLock|true
|
||||||
detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true
|
detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true
|
||||||
detectorFindUnsyncGet=FindUnsyncGet|true
|
detectorFindUnsyncGet=FindUnsyncGet|true
|
||||||
detectorFindUselessControlFlow=FindUselessControlFlow|true
|
detectorFindUselessControlFlow=FindUselessControlFlow|true
|
||||||
detectorFormatStringChecker=FormatStringChecker|true
|
detectorFormatStringChecker=FormatStringChecker|true
|
||||||
detectorHugeSharedStringConstants=HugeSharedStringConstants|true
|
detectorHugeSharedStringConstants=HugeSharedStringConstants|true
|
||||||
detectorIDivResultCastToDouble=IDivResultCastToDouble|true
|
detectorIDivResultCastToDouble=IDivResultCastToDouble|true
|
||||||
detectorIncompatMask=IncompatMask|true
|
detectorIncompatMask=IncompatMask|true
|
||||||
detectorInconsistentAnnotations=InconsistentAnnotations|true
|
detectorInconsistentAnnotations=InconsistentAnnotations|true
|
||||||
detectorInefficientMemberAccess=InefficientMemberAccess|false
|
detectorInefficientMemberAccess=InefficientMemberAccess|false
|
||||||
detectorInefficientToArray=InefficientToArray|true
|
detectorInefficientToArray=InefficientToArray|true
|
||||||
detectorInfiniteLoop=InfiniteLoop|true
|
detectorInfiniteLoop=InfiniteLoop|true
|
||||||
detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true
|
detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true
|
||||||
detectorInfiniteRecursiveLoop2=InfiniteRecursiveLoop2|false
|
detectorInfiniteRecursiveLoop2=InfiniteRecursiveLoop2|false
|
||||||
detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true
|
detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true
|
||||||
detectorInitializationChain=InitializationChain|true
|
detectorInitializationChain=InitializationChain|true
|
||||||
detectorInstantiateStaticClass=InstantiateStaticClass|true
|
detectorInstantiateStaticClass=InstantiateStaticClass|true
|
||||||
detectorInvalidJUnitTest=InvalidJUnitTest|true
|
detectorInvalidJUnitTest=InvalidJUnitTest|true
|
||||||
detectorIteratorIdioms=IteratorIdioms|true
|
detectorIteratorIdioms=IteratorIdioms|true
|
||||||
detectorLazyInit=LazyInit|true
|
detectorLazyInit=LazyInit|true
|
||||||
detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true
|
detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true
|
||||||
detectorLostLoggerDueToWeakReference=LostLoggerDueToWeakReference|true
|
detectorLostLoggerDueToWeakReference=LostLoggerDueToWeakReference|true
|
||||||
detectorMethodReturnCheck=MethodReturnCheck|true
|
detectorMethodReturnCheck=MethodReturnCheck|true
|
||||||
detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true
|
detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true
|
||||||
detectorMutableLock=MutableLock|true
|
detectorMutableLock=MutableLock|true
|
||||||
detectorMutableStaticFields=MutableStaticFields|true
|
detectorMutableStaticFields=MutableStaticFields|true
|
||||||
detectorNaming=Naming|true
|
detectorNaming=Naming|true
|
||||||
detectorNumberConstructor=NumberConstructor|true
|
detectorNumberConstructor=NumberConstructor|true
|
||||||
detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true
|
detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true
|
||||||
detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true
|
detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true
|
||||||
detectorPublicSemaphores=PublicSemaphores|false
|
detectorPublicSemaphores=PublicSemaphores|false
|
||||||
detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true
|
detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true
|
||||||
detectorReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass=ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass|true
|
detectorReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass=ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass|true
|
||||||
detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true
|
detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true
|
||||||
detectorRedundantInterfaces=RedundantInterfaces|true
|
detectorRedundantInterfaces=RedundantInterfaces|true
|
||||||
detectorRepeatedConditionals=RepeatedConditionals|true
|
detectorRepeatedConditionals=RepeatedConditionals|true
|
||||||
detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true
|
detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true
|
||||||
detectorSerializableIdiom=SerializableIdiom|true
|
detectorSerializableIdiom=SerializableIdiom|true
|
||||||
detectorStartInConstructor=StartInConstructor|true
|
detectorStartInConstructor=StartInConstructor|true
|
||||||
detectorStaticCalendarDetector=StaticCalendarDetector|true
|
detectorStaticCalendarDetector=StaticCalendarDetector|true
|
||||||
detectorStringConcatenation=StringConcatenation|true
|
detectorStringConcatenation=StringConcatenation|true
|
||||||
detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true
|
detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true
|
||||||
detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true
|
detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true
|
||||||
detectorSwitchFallthrough=SwitchFallthrough|true
|
detectorSwitchFallthrough=SwitchFallthrough|true
|
||||||
detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true
|
detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true
|
||||||
detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true
|
detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true
|
||||||
detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true
|
detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true
|
||||||
detectorURLProblems=URLProblems|true
|
detectorURLProblems=URLProblems|true
|
||||||
detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true
|
detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true
|
||||||
detectorUnnecessaryMath=UnnecessaryMath|true
|
detectorUnnecessaryMath=UnnecessaryMath|true
|
||||||
detectorUnreadFields=UnreadFields|true
|
detectorUnreadFields=UnreadFields|true
|
||||||
detectorUseObjectEquals=UseObjectEquals|false
|
detectorUseObjectEquals=UseObjectEquals|false
|
||||||
detectorUselessSubclassMethod=UselessSubclassMethod|false
|
detectorUselessSubclassMethod=UselessSubclassMethod|false
|
||||||
detectorVarArgsProblems=VarArgsProblems|true
|
detectorVarArgsProblems=VarArgsProblems|true
|
||||||
detectorVolatileUsage=VolatileUsage|true
|
detectorVolatileUsage=VolatileUsage|true
|
||||||
detectorWaitInLoop=WaitInLoop|true
|
detectorWaitInLoop=WaitInLoop|true
|
||||||
detectorWrongMapIterator=WrongMapIterator|true
|
detectorWrongMapIterator=WrongMapIterator|true
|
||||||
detectorXMLFactoryBypass=XMLFactoryBypass|true
|
detectorXMLFactoryBypass=XMLFactoryBypass|true
|
||||||
detector_threshold=2
|
detector_threshold=2
|
||||||
effort=default
|
effort=default
|
||||||
filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,MT_CORRECTNESS,PERFORMANCE,STYLE|false
|
filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,MT_CORRECTNESS,PERFORMANCE,STYLE|false
|
||||||
filter_settings_neg=MALICIOUS_CODE,NOISE,I18N,SECURITY,EXPERIMENTAL|
|
filter_settings_neg=MALICIOUS_CODE,NOISE,I18N,SECURITY,EXPERIMENTAL|
|
||||||
run_at_full_build=false
|
run_at_full_build=false
|
||||||
|
|||||||
21682
.gitattributes
vendored
21682
.gitattributes
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,284 +1,284 @@
|
|||||||
eclipse.preferences.version=1
|
eclipse.preferences.version=1
|
||||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||||
org.eclipse.jdt.core.compiler.source=1.7
|
org.eclipse.jdt.core.compiler.source=1.7
|
||||||
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
|
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
|
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
|
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
|
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
|
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
|
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
|
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
|
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
|
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
|
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
|
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
|
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
|
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
|
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
|
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
|
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
|
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
|
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
|
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
|
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
|
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
|
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
|
||||||
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
|
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
|
||||||
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
|
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
|
||||||
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
|
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
|
||||||
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
|
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
|
||||||
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
|
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
|
||||||
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
|
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
|
||||||
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
|
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
|
||||||
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
|
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
|
||||||
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
|
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
|
||||||
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
|
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
|
||||||
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
|
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
|
||||||
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
|
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
|
||||||
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
|
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
|
||||||
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
|
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
|
||||||
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
|
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
|
||||||
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
|
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
|
||||||
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
|
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
|
||||||
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
|
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
|
||||||
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
|
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
|
||||||
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
|
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
|
||||||
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
|
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
|
||||||
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
|
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
|
||||||
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
|
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
|
||||||
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
|
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
|
||||||
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
|
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
|
||||||
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
|
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
|
||||||
org.eclipse.jdt.core.formatter.comment.format_header=false
|
org.eclipse.jdt.core.formatter.comment.format_header=false
|
||||||
org.eclipse.jdt.core.formatter.comment.format_html=true
|
org.eclipse.jdt.core.formatter.comment.format_html=true
|
||||||
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
|
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
|
||||||
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
|
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
|
||||||
org.eclipse.jdt.core.formatter.comment.format_source_code=true
|
org.eclipse.jdt.core.formatter.comment.format_source_code=true
|
||||||
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
|
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
|
||||||
org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
|
org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
|
||||||
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
|
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
|
||||||
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
|
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
|
||||||
org.eclipse.jdt.core.formatter.comment.line_length=80
|
org.eclipse.jdt.core.formatter.comment.line_length=80
|
||||||
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
|
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
|
||||||
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
|
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
|
||||||
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
|
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
|
||||||
org.eclipse.jdt.core.formatter.compact_else_if=true
|
org.eclipse.jdt.core.formatter.compact_else_if=true
|
||||||
org.eclipse.jdt.core.formatter.continuation_indentation=2
|
org.eclipse.jdt.core.formatter.continuation_indentation=2
|
||||||
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
|
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
|
||||||
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
|
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
|
||||||
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
|
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
|
||||||
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
|
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
|
||||||
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
|
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
|
||||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
|
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
|
||||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
|
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
|
||||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
|
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
|
||||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
|
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
|
||||||
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
|
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
|
||||||
org.eclipse.jdt.core.formatter.indent_empty_lines=false
|
org.eclipse.jdt.core.formatter.indent_empty_lines=false
|
||||||
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
|
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
|
||||||
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
|
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
|
||||||
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
|
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
|
||||||
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
|
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
|
||||||
org.eclipse.jdt.core.formatter.indentation.size=4
|
org.eclipse.jdt.core.formatter.indentation.size=4
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
|
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
|
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
|
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
|
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
|
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
|
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
|
org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
|
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
|
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
|
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
|
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
|
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
|
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
|
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
|
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
|
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
|
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
|
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
|
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
|
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
|
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
|
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
|
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
|
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
|
||||||
org.eclipse.jdt.core.formatter.join_lines_in_comments=true
|
org.eclipse.jdt.core.formatter.join_lines_in_comments=true
|
||||||
org.eclipse.jdt.core.formatter.join_wrapped_lines=true
|
org.eclipse.jdt.core.formatter.join_wrapped_lines=true
|
||||||
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
|
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
|
||||||
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
|
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
|
||||||
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
|
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
|
||||||
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
|
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
|
||||||
org.eclipse.jdt.core.formatter.lineSplit=120
|
org.eclipse.jdt.core.formatter.lineSplit=120
|
||||||
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
|
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
|
||||||
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
|
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
|
||||||
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
|
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
|
||||||
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
|
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
|
||||||
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
|
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
|
||||||
org.eclipse.jdt.core.formatter.tabulation.char=space
|
org.eclipse.jdt.core.formatter.tabulation.char=space
|
||||||
org.eclipse.jdt.core.formatter.tabulation.size=4
|
org.eclipse.jdt.core.formatter.tabulation.size=4
|
||||||
org.eclipse.jdt.core.formatter.use_on_off_tags=false
|
org.eclipse.jdt.core.formatter.use_on_off_tags=false
|
||||||
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true
|
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true
|
||||||
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
|
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
|
||||||
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
|
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
21
checkstyle.xml
Normal file
21
checkstyle.xml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE module PUBLIC
|
||||||
|
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||||
|
"http://checkstyle.sourceforge.net/dtds/configuration_1_3.dtd">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Checkstyle is very configurable.
|
||||||
|
http://checkstyle.sf.net (or in your downloaded distribution).
|
||||||
|
-->
|
||||||
|
|
||||||
|
<module name="Checker">
|
||||||
|
<module name="TreeWalker">
|
||||||
|
|
||||||
|
<module name="RedundantImport"/>
|
||||||
|
<module name="UnusedImports">
|
||||||
|
<!-- <property name="processJavadoc" value="false"/> -->
|
||||||
|
</module>
|
||||||
|
|
||||||
|
</module>
|
||||||
|
|
||||||
|
</module>
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<classpath>
|
<classpath>
|
||||||
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
|
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
|
||||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
|
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
|
||||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
|
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
|
||||||
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||||
<classpathentry kind="output" path="target/classes"/>
|
<classpathentry combineaccessrules="false" kind="src" path="/forge-core"/>
|
||||||
</classpath>
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
|
</classpath>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<name>forge-ai</name>
|
<name>forge-ai</name>
|
||||||
<comment></comment>
|
<comment></comment>
|
||||||
<projects>
|
<projects>
|
||||||
|
<project>forge-game</project>
|
||||||
</projects>
|
</projects>
|
||||||
<buildSpec>
|
<buildSpec>
|
||||||
<buildCommand>
|
<buildCommand>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
eclipse.preferences.version=1
|
eclipse.preferences.version=1
|
||||||
encoding//src/main/java=ISO-8859-1
|
encoding//src/main/java=ISO-8859-1
|
||||||
encoding//src/test/java=ISO-8859-1
|
encoding/<project>=UTF-8
|
||||||
encoding/<project>=UTF-8
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>1.6.1</version>
|
<version>1.6.16</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-ai</artifactId>
|
<artifactId>forge-ai</artifactId>
|
||||||
@@ -29,4 +29,31 @@
|
|||||||
<version>3.6.1</version>
|
<version>3.6.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>checkstyle-validation</id>
|
||||||
|
<phase>validate</phase>
|
||||||
|
<configuration>
|
||||||
|
<configLocation>../checkstyle.xml</configLocation>
|
||||||
|
<includeTestSourceDirectory>true</includeTestSourceDirectory>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
<consoleOutput>true</consoleOutput>
|
||||||
|
<failsOnError>true</failsOnError>
|
||||||
|
<failOnViolation>true</failOnViolation>
|
||||||
|
</configuration>
|
||||||
|
<goals>
|
||||||
|
<goal>check</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package forge;
|
package forge.ai;
|
||||||
|
|
||||||
public enum AIOption {
|
public enum AIOption {
|
||||||
USE_SIMULATION;
|
USE_SIMULATION;
|
||||||
@@ -17,14 +17,9 @@
|
|||||||
*/
|
*/
|
||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.ai.ability.AnimateAi;
|
import forge.ai.ability.AnimateAi;
|
||||||
import forge.card.CardTypeView;
|
import forge.card.CardTypeView;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
@@ -35,14 +30,21 @@ import forge.game.card.*;
|
|||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.combat.GlobalAttackRestrictions;
|
import forge.game.combat.GlobalAttackRestrictions;
|
||||||
|
import forge.game.keyword.Keyword;
|
||||||
|
import forge.game.keyword.KeywordInterface;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.Expressions;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
import forge.util.TextUtil;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
//doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer()
|
//doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer()
|
||||||
/**
|
/**
|
||||||
@@ -59,9 +61,6 @@ public class AiAttackController {
|
|||||||
private final List<Card> attackers;
|
private final List<Card> attackers;
|
||||||
private final List<Card> blockers;
|
private final List<Card> blockers;
|
||||||
|
|
||||||
private final static Random random = new Random();
|
|
||||||
private final static int randomInt = random.nextInt();
|
|
||||||
|
|
||||||
private List<Card> oppList; // holds human player creatures
|
private List<Card> oppList; // holds human player creatures
|
||||||
private List<Card> myList; // holds computer creatures
|
private List<Card> myList; // holds computer creatures
|
||||||
|
|
||||||
@@ -104,7 +103,7 @@ public class AiAttackController {
|
|||||||
} // overloaded constructor to evaluate single specified attacker
|
} // overloaded constructor to evaluate single specified attacker
|
||||||
|
|
||||||
public static List<Card> getOpponentCreatures(final Player defender) {
|
public static List<Card> getOpponentCreatures(final Player defender) {
|
||||||
List<Card> defenders = Lists.newArrayList();
|
List<Card> defenders = new ArrayList<Card>();
|
||||||
defenders.addAll(defender.getCreaturesInPlay());
|
defenders.addAll(defender.getCreaturesInPlay());
|
||||||
Predicate<Card> canAnimate = new Predicate<Card>() {
|
Predicate<Card> canAnimate = new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -203,8 +202,11 @@ public class AiAttackController {
|
|||||||
if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) {
|
if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.attackers.size() == 1 && attacker.hasKeyword("Exalted")
|
|
||||||
&& ComputerUtilCombat.predictDamageTo(opp, 1, attacker, true) > 0) {
|
// TODO check if that makes sense
|
||||||
|
int exalted = ai.countExaltedBonus();
|
||||||
|
if (this.attackers.size() == 1 && exalted > 0
|
||||||
|
&& ComputerUtilCombat.predictDamageTo(opp, exalted, attacker, true) > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,6 +245,19 @@ public class AiAttackController {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final static Card getCardCanBlockAnAttacker(final Card c, final List<Card> attackers, final boolean nextTurn) {
|
||||||
|
final List<Card> attackerList = new ArrayList<Card>(attackers);
|
||||||
|
if (!c.isCreature()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (final Card attacker : attackerList) {
|
||||||
|
if (CombatUtil.canBlock(attacker, c, nextTurn)) {
|
||||||
|
return attacker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// this checks to make sure that the computer player doesn't lose when the human player attacks
|
// this checks to make sure that the computer player doesn't lose when the human player attacks
|
||||||
// this method is used by getAttackers()
|
// this method is used by getAttackers()
|
||||||
public final List<Card> notNeededAsBlockers(final Player ai, final List<Card> attackers) {
|
public final List<Card> notNeededAsBlockers(final Player ai, final List<Card> attackers) {
|
||||||
@@ -253,6 +268,40 @@ public class AiAttackController {
|
|||||||
if (ai.getGame().getPhaseHandler().getNextTurn().equals(ai)) {
|
if (ai.getGame().getPhaseHandler().getNextTurn().equals(ai)) {
|
||||||
return attackers;
|
return attackers;
|
||||||
}
|
}
|
||||||
|
// no need to block (already holding mana to cast fog next turn)
|
||||||
|
if (!AiCardMemory.isMemorySetEmpty(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT)) {
|
||||||
|
// Don't send the card that'll do the fog effect to attack, it's unsafe!
|
||||||
|
|
||||||
|
List<Card> toRemove = Lists.newArrayList();
|
||||||
|
for(Card c : attackers) {
|
||||||
|
if (AiCardMemory.isRememberedCard(ai, c, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT)) {
|
||||||
|
toRemove.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attackers.removeAll(toRemove);
|
||||||
|
|
||||||
|
return attackers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no need to block if an effect is in play which untaps all creatures (pseudo-Vigilance akin to
|
||||||
|
// Awakening or Prophet of Kruphix)
|
||||||
|
for (Card card : ai.getGame().getCardsIn(ZoneType.Battlefield)) {
|
||||||
|
boolean untapsEachTurn = card.hasSVar("UntapsEachTurn");
|
||||||
|
boolean untapsEachOtherTurn = card.hasSVar("UntapsEachOtherPlayerTurn");
|
||||||
|
|
||||||
|
if (untapsEachTurn || untapsEachOtherTurn) {
|
||||||
|
String affected = untapsEachTurn ? card.getSVar("UntapsEachTurn")
|
||||||
|
: card.getSVar("UntapsEachOtherPlayerTurn");
|
||||||
|
|
||||||
|
for (String aff : TextUtil.split(affected, ',')) {
|
||||||
|
if (aff.equals("Creature")
|
||||||
|
&& (untapsEachTurn || (untapsEachOtherTurn && ai.equals(card.getController())))) {
|
||||||
|
return attackers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<Card> opponentsAttackers = new ArrayList<Card>(oppList);
|
List<Card> opponentsAttackers = new ArrayList<Card>(oppList);
|
||||||
opponentsAttackers = CardLists.filter(opponentsAttackers, new Predicate<Card>() {
|
opponentsAttackers = CardLists.filter(opponentsAttackers, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -271,7 +320,7 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (c.hasKeyword("Vigilance")) {
|
if (c.hasKeyword(Keyword.VIGILANCE)) {
|
||||||
vigilantes.add(c);
|
vigilantes.add(c);
|
||||||
notNeededAsBlockers.remove(c); // they will be re-added later
|
notNeededAsBlockers.remove(c); // they will be re-added later
|
||||||
if (canBlockAnAttacker(c, opponentsAttackers, false)) {
|
if (canBlockAnAttacker(c, opponentsAttackers, false)) {
|
||||||
@@ -313,7 +362,7 @@ public class AiAttackController {
|
|||||||
// In addition, if the computer guesses it needs no blockers, make sure
|
// In addition, if the computer guesses it needs no blockers, make sure
|
||||||
// that
|
// that
|
||||||
// it won't be surprised by Exalted
|
// it won't be surprised by Exalted
|
||||||
final int humanExaltedBonus = countExaltedBonus(opp);
|
final int humanExaltedBonus = opp.countExaltedBonus();
|
||||||
|
|
||||||
if (humanExaltedBonus > 0) {
|
if (humanExaltedBonus > 0) {
|
||||||
final boolean finestHour = opp.isCardInPlay("Finest Hour");
|
final boolean finestHour = opp.isCardInPlay("Finest Hour");
|
||||||
@@ -362,8 +411,12 @@ public class AiAttackController {
|
|||||||
blockersLeft--;
|
blockersLeft--;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
totalAttack += ComputerUtilCombat.damageIfUnblocked(attacker, ai, null, false);
|
|
||||||
totalPoison += ComputerUtilCombat.poisonIfUnblocked(attacker, ai);
|
// Test for some special triggers that can change the creature in combat
|
||||||
|
Card effectiveAttacker = ComputerUtilCombat.applyPotentialAttackCloneTriggers(attacker);
|
||||||
|
|
||||||
|
totalAttack += ComputerUtilCombat.damageIfUnblocked(effectiveAttacker, ai, null, false);
|
||||||
|
totalPoison += ComputerUtilCombat.poisonIfUnblocked(effectiveAttacker, ai);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalAttack > 0 && ai.getLife() <= totalAttack && !ai.cantLoseForZeroOrLessLife()) {
|
if (totalAttack > 0 && ai.getLife() <= totalAttack && !ai.cantLoseForZeroOrLessLife()) {
|
||||||
@@ -402,7 +455,7 @@ public class AiAttackController {
|
|||||||
CardCollectionView oppBattlefield = c.getController().getCardsIn(ZoneType.Battlefield);
|
CardCollectionView oppBattlefield = c.getController().getCardsIn(ZoneType.Battlefield);
|
||||||
|
|
||||||
if (c.getName().equals("Heart of Kiran")) {
|
if (c.getName().equals("Heart of Kiran")) {
|
||||||
if (!CardLists.filter(oppBattlefield, CardPredicates.Presets.PLANEWALKERS).isEmpty()) {
|
if (!CardLists.filter(oppBattlefield, CardPredicates.Presets.PLANESWALKERS).isEmpty()) {
|
||||||
// can be activated by removing a loyalty counter instead of tapping a creature
|
// can be activated by removing a loyalty counter instead of tapping a creature
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -424,16 +477,45 @@ public class AiAttackController {
|
|||||||
|
|
||||||
final Player opp = this.defendingOpponent;
|
final Player opp = this.defendingOpponent;
|
||||||
|
|
||||||
for (Card attacker : attackers) {
|
// if true, the AI will attempt to identify which blockers will already be taken,
|
||||||
if (!CombatUtil.canBeBlocked(attacker, this.blockers, null)
|
// thus attempting to predict how many creatures with evasion can actively block
|
||||||
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
boolean predictEvasion = false;
|
||||||
unblockedAttackers.add(attacker);
|
if (ai.getController().isAI()) {
|
||||||
|
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||||
|
if (aic.getBooleanProperty(AiProps.COMBAT_ASSAULT_ATTACK_EVASION_PREDICTION)) {
|
||||||
|
predictEvasion = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CardCollection accountedBlockers = new CardCollection(this.blockers);
|
||||||
|
CardCollection categorizedAttackers = new CardCollection();
|
||||||
|
|
||||||
|
if (predictEvasion) {
|
||||||
|
// split categorizedAttackers such that the ones with evasion come first and
|
||||||
|
// can be properly accounted for. Note that at this point the attackers need
|
||||||
|
// to be sorted by power already (see the Collections.sort call above).
|
||||||
|
categorizedAttackers.addAll(ComputerUtilCombat.categorizeAttackersByEvasion(this.attackers));
|
||||||
|
} else {
|
||||||
|
categorizedAttackers.addAll(this.attackers);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Card attacker : categorizedAttackers) {
|
||||||
|
if (!CombatUtil.canBeBlocked(attacker, accountedBlockers, null)
|
||||||
|
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
||||||
|
unblockedAttackers.add(attacker);
|
||||||
|
} else {
|
||||||
|
if (predictEvasion) {
|
||||||
|
List<Card> potentialBestBlockers = CombatUtil.getPotentialBestBlockers(attacker, accountedBlockers, null);
|
||||||
|
accountedBlockers.removeAll(potentialBestBlockers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingAttackers.removeAll(unblockedAttackers);
|
||||||
|
|
||||||
for (Card blocker : this.blockers) {
|
for (Card blocker : this.blockers) {
|
||||||
if (blocker.hasKeyword("CARDNAME can block any number of creatures.")
|
if (blocker.hasKeyword("CARDNAME can block any number of creatures.")
|
||||||
|| blocker.hasKeyword("CARDNAME can block an additional ninety-nine creatures.")) {
|
|| blocker.hasKeyword("CARDNAME can block an additional ninety-nine creatures each combat.")) {
|
||||||
for (Card attacker : this.attackers) {
|
for (Card attacker : this.attackers) {
|
||||||
if (CombatUtil.canBlock(attacker, blocker)) {
|
if (CombatUtil.canBlock(attacker, blocker)) {
|
||||||
remainingAttackers.remove(attacker);
|
remainingAttackers.remove(attacker);
|
||||||
@@ -449,7 +531,7 @@ public class AiAttackController {
|
|||||||
if (remainingAttackers.isEmpty() || maxBlockersAfterCrew == 0) {
|
if (remainingAttackers.isEmpty() || maxBlockersAfterCrew == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (blocker.hasKeyword("CARDNAME can block an additional creature.")) {
|
if (blocker.hasKeyword("CARDNAME can block an additional creature each combat.")) {
|
||||||
blockedAttackers.add(remainingAttackers.get(0));
|
blockedAttackers.add(remainingAttackers.get(0));
|
||||||
remainingAttackers.remove(0);
|
remainingAttackers.remove(0);
|
||||||
maxBlockersAfterCrew--;
|
maxBlockersAfterCrew--;
|
||||||
@@ -465,7 +547,7 @@ public class AiAttackController {
|
|||||||
|
|
||||||
int trampleDamage = 0;
|
int trampleDamage = 0;
|
||||||
for (Card attacker : blockedAttackers) {
|
for (Card attacker : blockedAttackers) {
|
||||||
if (attacker.hasKeyword("Trample")) {
|
if (attacker.hasKeyword(Keyword.TRAMPLE)) {
|
||||||
int damage = ComputerUtilCombat.getAttack(attacker);
|
int damage = ComputerUtilCombat.getAttack(attacker);
|
||||||
for (Card blocker : this.blockers) {
|
for (Card blocker : this.blockers) {
|
||||||
if (CombatUtil.canBlock(attacker, blocker)) {
|
if (CombatUtil.canBlock(attacker, blocker)) {
|
||||||
@@ -478,13 +560,15 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + ComputerUtil.possibleNonCombatDamage(ai)
|
int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage;
|
||||||
+ trampleDamage >= opp.getLife()
|
int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, opp);
|
||||||
|
|
||||||
|
if (totalCombatDamage + ComputerUtil.possibleNonCombatDamage(ai) >= opp.getLife()
|
||||||
&& !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && opp.getLife() < 1)) {
|
&& !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && opp.getLife() < 1)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, opp) >= 10 - opp.getPoisonCounters()) {
|
if (totalPoisonDamage >= 10 - opp.getPoisonCounters()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,11 +582,16 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
Player prefDefender = (Player) (defs.contains(this.defendingOpponent) ? this.defendingOpponent : defs.get(0));
|
Player prefDefender = (Player) (defs.contains(this.defendingOpponent) ? this.defendingOpponent : defs.get(0));
|
||||||
|
|
||||||
final GameEntity entity = ai.getMustAttackEntity();
|
// Attempt to see if there's a defined entity that must be attacked strictly this turn...
|
||||||
|
GameEntity entity = ai.getMustAttackEntityThisTurn();
|
||||||
|
if (entity == null) {
|
||||||
|
// ...or during the attacking creature controller's turn
|
||||||
|
entity = ai.getMustAttackEntity();
|
||||||
|
}
|
||||||
if (null != entity) {
|
if (null != entity) {
|
||||||
int n = defs.indexOf(entity);
|
int n = defs.indexOf(entity);
|
||||||
if (-1 == n) {
|
if (-1 == n) {
|
||||||
System.out.println("getMustAttackEntity() returned something not in defenders.");
|
System.out.println("getMustAttackEntity() or getMustAttackEntityThisTurn() returned something not in defenders.");
|
||||||
return prefDefender;
|
return prefDefender;
|
||||||
} else {
|
} else {
|
||||||
return entity;
|
return entity;
|
||||||
@@ -532,22 +621,26 @@ public class AiAttackController {
|
|||||||
* @return a {@link forge.game.combat.Combat} object.
|
* @return a {@link forge.game.combat.Combat} object.
|
||||||
*/
|
*/
|
||||||
public final void declareAttackers(final Combat combat) {
|
public final void declareAttackers(final Combat combat) {
|
||||||
// if this method is called multiple times during a turn,
|
|
||||||
// it will always return the same value
|
|
||||||
// randomInt is used so that the computer doesn't always
|
|
||||||
// do the same thing on turn 3 if he had the same creatures in play
|
|
||||||
// I know this is a little confusing
|
|
||||||
|
|
||||||
random.setSeed(ai.getGame().getPhaseHandler().getTurn() + AiAttackController.randomInt);
|
|
||||||
|
|
||||||
if (this.attackers.isEmpty()) {
|
if (this.attackers.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Aggro options
|
||||||
boolean playAggro = false;
|
boolean playAggro = false;
|
||||||
|
int chanceToAttackToTrade = 0;
|
||||||
|
boolean tradeIfTappedOut = false;
|
||||||
|
int extraChanceIfOppHasMana = 0;
|
||||||
|
boolean tradeIfLowerLifePressure = false;
|
||||||
if (ai.getController().isAI()) {
|
if (ai.getController().isAI()) {
|
||||||
playAggro = ((PlayerControllerAi) ai.getController()).getAi().getProperty(AiProps.PLAY_AGGRO).equals("true");
|
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||||
|
playAggro = aic.getBooleanProperty(AiProps.PLAY_AGGRO);
|
||||||
|
chanceToAttackToTrade = aic.getIntProperty(AiProps.CHANCE_TO_ATTACK_INTO_TRADE);
|
||||||
|
tradeIfTappedOut = aic.getBooleanProperty(AiProps.ATTACK_INTO_TRADE_WHEN_TAPPED_OUT);
|
||||||
|
extraChanceIfOppHasMana = aic.getIntProperty(AiProps.CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA);
|
||||||
|
tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean bAssault = this.doAssault(ai);
|
final boolean bAssault = this.doAssault(ai);
|
||||||
// TODO: detect Lightmine Field by presence of a card with a specific trigger
|
// TODO: detect Lightmine Field by presence of a card with a specific trigger
|
||||||
final boolean lightmineField = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Lightmine Field");
|
final boolean lightmineField = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Lightmine Field");
|
||||||
@@ -558,7 +651,11 @@ public class AiAttackController {
|
|||||||
|
|
||||||
// TODO probably use AttackConstraints instead of only GlobalAttackRestrictions?
|
// TODO probably use AttackConstraints instead of only GlobalAttackRestrictions?
|
||||||
GlobalAttackRestrictions restrict = GlobalAttackRestrictions.getGlobalRestrictions(ai, combat.getDefenders());
|
GlobalAttackRestrictions restrict = GlobalAttackRestrictions.getGlobalRestrictions(ai, combat.getDefenders());
|
||||||
final int attackMax = restrict.getMax();
|
int attackMax = restrict.getMax();
|
||||||
|
if (attackMax == -1) {
|
||||||
|
// check with the local limitations vs. the chosen defender
|
||||||
|
attackMax = ComputerUtilCombat.getMaxAttackersFor(defender);
|
||||||
|
}
|
||||||
|
|
||||||
if (attackMax == 0) {
|
if (attackMax == 0) {
|
||||||
// can't attack anymore
|
// can't attack anymore
|
||||||
@@ -581,7 +678,8 @@ public class AiAttackController {
|
|||||||
&& isEffectiveAttacker(ai, attacker, combat)) {
|
&& isEffectiveAttacker(ai, attacker, combat)) {
|
||||||
mustAttack = true;
|
mustAttack = true;
|
||||||
} else {
|
} else {
|
||||||
for (String s : attacker.getKeywords()) {
|
for (KeywordInterface inst : attacker.getKeywords()) {
|
||||||
|
String s = inst.getOriginal();
|
||||||
if (s.equals("CARDNAME attacks each turn if able.")
|
if (s.equals("CARDNAME attacks each turn if able.")
|
||||||
|| s.startsWith("CARDNAME attacks specific player each combat if able")
|
|| s.startsWith("CARDNAME attacks specific player each combat if able")
|
||||||
|| s.equals("CARDNAME attacks each combat if able.")) {
|
|| s.equals("CARDNAME attacks each combat if able.")) {
|
||||||
@@ -590,7 +688,7 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mustAttack || attacker.getController().getMustAttackEntity() != null) {
|
if (mustAttack || attacker.getController().getMustAttackEntity() != null || attacker.getController().getMustAttackEntityThisTurn() != null) {
|
||||||
combat.addAttacker(attacker, defender);
|
combat.addAttacker(attacker, defender);
|
||||||
attackersLeft.remove(attacker);
|
attackersLeft.remove(attacker);
|
||||||
numForcedAttackers++;
|
numForcedAttackers++;
|
||||||
@@ -635,20 +733,15 @@ public class AiAttackController {
|
|||||||
|
|
||||||
// Exalted
|
// Exalted
|
||||||
if (combat.getAttackers().isEmpty()) {
|
if (combat.getAttackers().isEmpty()) {
|
||||||
boolean exalted = false;
|
boolean exalted = ai.countExaltedBonus() > 2;
|
||||||
int exaltedCount = 0;
|
|
||||||
for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
|
if (!exalted) {
|
||||||
if (c.getName().equals("Rafiq of the Many") || c.getName().equals("Battlegrace Angel")) {
|
for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
|
||||||
exalted = true;
|
if (c.getName().equals("Rafiq of the Many") || c.getName().equals("Battlegrace Angel")) {
|
||||||
break;
|
exalted = true;
|
||||||
}
|
break;
|
||||||
if (c.getName().equals("Finest Hour") && ai.getGame().getPhaseHandler().isFirstCombat()) {
|
}
|
||||||
exalted = true;
|
if (c.getName().equals("Finest Hour") && ai.getGame().getPhaseHandler().isFirstCombat()) {
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (c.hasKeyword("Exalted")) {
|
|
||||||
exaltedCount++;
|
|
||||||
if (exaltedCount > 2) {
|
|
||||||
exalted = true;
|
exalted = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -711,7 +804,20 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final Card pCard : this.oppList) {
|
boolean predictEvasion = (ai.getController().isAI()
|
||||||
|
&& ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION));
|
||||||
|
|
||||||
|
CardCollection categorizedOppList = new CardCollection();
|
||||||
|
if (predictEvasion) {
|
||||||
|
// If predicting evasion, make sure that attackers with evasion are considered first
|
||||||
|
// (to avoid situations where the AI would predict his non-flyers to be blocked with
|
||||||
|
// flying creatures and then believe that flyers will necessarily be left unblocked)
|
||||||
|
categorizedOppList.addAll(ComputerUtilCombat.categorizeAttackersByEvasion(this.oppList));
|
||||||
|
} else {
|
||||||
|
categorizedOppList.addAll(this.oppList);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final Card pCard : categorizedOppList) {
|
||||||
// if the creature can attack next turn add it to counter attackers list
|
// if the creature can attack next turn add it to counter attackers list
|
||||||
if (ComputerUtilCombat.canAttackNextTurn(pCard) && pCard.getNetCombatDamage() > 0) {
|
if (ComputerUtilCombat.canAttackNextTurn(pCard) && pCard.getNetCombatDamage() > 0) {
|
||||||
nextTurnAttackers.add(pCard);
|
nextTurnAttackers.add(pCard);
|
||||||
@@ -719,8 +825,13 @@ public class AiAttackController {
|
|||||||
humanForces += 1; // player forces they might use to attack
|
humanForces += 1; // player forces they might use to attack
|
||||||
}
|
}
|
||||||
// increment player forces that are relevant to an attritional attack - includes walls
|
// increment player forces that are relevant to an attritional attack - includes walls
|
||||||
if (canBlockAnAttacker(pCard, candidateAttackers, true)) {
|
|
||||||
|
Card potentialOppBlocker = getCardCanBlockAnAttacker(pCard, candidateAttackers, true);
|
||||||
|
if (potentialOppBlocker != null) {
|
||||||
humanForcesForAttritionalAttack += 1;
|
humanForcesForAttritionalAttack += 1;
|
||||||
|
if (predictEvasion) {
|
||||||
|
candidateAttackers.remove(potentialOppBlocker);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -847,8 +958,18 @@ public class AiAttackController {
|
|||||||
if (ratioDiff > 0 && doAttritionalAttack) {
|
if (ratioDiff > 0 && doAttritionalAttack) {
|
||||||
this.aiAggression = 5; // attack at all costs
|
this.aiAggression = 5; // attack at all costs
|
||||||
} else if ((ratioDiff >= 1 && this.attackers.size() > 1 && (humanLifeToDamageRatio < 2 || outNumber > 0))
|
} else if ((ratioDiff >= 1 && this.attackers.size() > 1 && (humanLifeToDamageRatio < 2 || outNumber > 0))
|
||||||
|| (playAggro && humanLifeToDamageRatio > 1)) {
|
|| (playAggro && MyRandom.percentTrue(chanceToAttackToTrade) && humanLifeToDamageRatio > 1)) {
|
||||||
this.aiAggression = 4; // attack expecting to trade or damage player.
|
this.aiAggression = 4; // attack expecting to trade or damage player.
|
||||||
|
} else if (MyRandom.percentTrue(chanceToAttackToTrade) && humanLifeToDamageRatio > 1
|
||||||
|
&& defendingOpponent != null
|
||||||
|
&& ComputerUtil.countUsefulCreatures(ai) > ComputerUtil.countUsefulCreatures(defendingOpponent)
|
||||||
|
&& ai.getLife() > defendingOpponent.getLife()
|
||||||
|
&& !ComputerUtilCombat.lifeInDanger(ai, combat)
|
||||||
|
&& (ComputerUtilMana.getAvailableManaEstimate(ai) > 0) || tradeIfTappedOut
|
||||||
|
&& (ComputerUtilMana.getAvailableManaEstimate(defendingOpponent) == 0) || MyRandom.percentTrue(extraChanceIfOppHasMana)
|
||||||
|
&& (!tradeIfLowerLifePressure || (ai.getLifeLostLastTurn() + ai.getLifeLostThisTurn() <
|
||||||
|
defendingOpponent.getLifeLostThisTurn() + defendingOpponent.getLifeLostThisTurn()))) {
|
||||||
|
this.aiAggression = 4; // random (chance-based) attack expecting to trade or damage player.
|
||||||
} else if (ratioDiff >= 0 && this.attackers.size() > 1) {
|
} else if (ratioDiff >= 0 && this.attackers.size() > 1) {
|
||||||
this.aiAggression = 3; // attack expecting to make good trades or damage player.
|
this.aiAggression = 3; // attack expecting to make good trades or damage player.
|
||||||
} else if (ratioDiff + outNumber >= -1 || aiLifeToPlayerDamageRatio > 1
|
} else if (ratioDiff + outNumber >= -1 || aiLifeToPlayerDamageRatio > 1
|
||||||
@@ -927,24 +1048,6 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
} // getAttackers()
|
} // getAttackers()
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* countExaltedBonus.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param player
|
|
||||||
* a {@link forge.game.player.Player} object.
|
|
||||||
* @return a int.
|
|
||||||
*/
|
|
||||||
public final static int countExaltedBonus(final Player player) {
|
|
||||||
int bonus = 0;
|
|
||||||
for (Card c : player.getCardsIn(ZoneType.Battlefield)) {
|
|
||||||
bonus += c.getAmountOfKeyword("Exalted");
|
|
||||||
}
|
|
||||||
|
|
||||||
return bonus;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* getAttack.
|
* getAttack.
|
||||||
@@ -957,7 +1060,7 @@ public class AiAttackController {
|
|||||||
public final static int getAttack(final Card c) {
|
public final static int getAttack(final Card c) {
|
||||||
int n = c.getNetCombatDamage();
|
int n = c.getNetCombatDamage();
|
||||||
|
|
||||||
if (c.hasKeyword("Double Strike")) {
|
if (c.hasKeyword(Keyword.DOUBLE_STRIKE)) {
|
||||||
n *= 2;
|
n *= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -985,7 +1088,29 @@ public class AiAttackController {
|
|||||||
boolean isWorthLessThanAllKillers = true;
|
boolean isWorthLessThanAllKillers = true;
|
||||||
boolean canBeBlocked = false;
|
boolean canBeBlocked = false;
|
||||||
int numberOfPossibleBlockers = 0;
|
int numberOfPossibleBlockers = 0;
|
||||||
|
|
||||||
|
// Is it a creature that has a more valuable ability with a tap cost than what it can do by attacking?
|
||||||
|
if ((attacker.hasSVar("NonCombatPriority"))
|
||||||
|
&& (!attacker.hasKeyword(Keyword.VIGILANCE))) {
|
||||||
|
// For each level of priority, enemy has to have life as much as the creature's power
|
||||||
|
// so a priority of 4 means the creature will not attack unless it can defeat that player in 4 successful attacks.
|
||||||
|
// the lower the priroity, the less willing the AI is to use the creature for attacking.
|
||||||
|
// TODO Somehow subtract expected damage of other attacking creatures from enemy life total (how? other attackers not yet declared? Can the AI guesstimate which of their creatures will not get blocked?)
|
||||||
|
if (attacker.getCurrentPower() * Integer.parseInt(attacker.getSVar("NonCombatPriority")) < ai.getOpponentsSmallestLifeTotal()) {
|
||||||
|
// Check if the card actually has an ability the AI can and wants to play, if not, attacking is fine!
|
||||||
|
boolean wantability = false;
|
||||||
|
for (SpellAbility sa : attacker.getSpellAbilities()) {
|
||||||
|
// Do not attack if we can afford using the ability.
|
||||||
|
if (sa.isAbility()) {
|
||||||
|
if (ComputerUtilCost.canPayCost(sa, ai)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// TODO Eventually The Ai will need to learn to predict if they have any use for the ability before next untap or not.
|
||||||
|
// TODO abilities that tap enemy creatures should probably only be saved if the enemy has nonzero creatures? Haste can be a threat though...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.isEffectiveAttacker(ai, attacker, combat)) {
|
if (!this.isEffectiveAttacker(ai, attacker, combat)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -994,9 +1119,14 @@ public class AiAttackController {
|
|||||||
// is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...)
|
// is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...)
|
||||||
boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE")
|
boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE")
|
||||||
|| "Blocked".equals(attacker.getSVar("HasAttackEffect"));
|
|| "Blocked".equals(attacker.getSVar("HasAttackEffect"));
|
||||||
|
// total power of the defending creatures, used in predicting whether a gang block can kill the attacker
|
||||||
|
int defPower = CardLists.getTotalPower(defenders, true);
|
||||||
|
|
||||||
if (!hasCombatEffect) {
|
if (!hasCombatEffect) {
|
||||||
for (String keyword : attacker.getKeywords()) {
|
for (KeywordInterface inst : attacker.getKeywords()) {
|
||||||
if (keyword.equals("Wither") || keyword.equals("Infect") || keyword.equals("Lifelink")) {
|
String keyword = inst.getOriginal();
|
||||||
|
if (keyword.equals("Wither") || keyword.equals("Infect")
|
||||||
|
|| keyword.equals("Lifelink") || keyword.startsWith("Afflict")) {
|
||||||
hasCombatEffect = true;
|
hasCombatEffect = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1013,7 +1143,7 @@ public class AiAttackController {
|
|||||||
&& CombatUtil.canBlock(attacker, defender)) {
|
&& CombatUtil.canBlock(attacker, defender)) {
|
||||||
numberOfPossibleBlockers += 1;
|
numberOfPossibleBlockers += 1;
|
||||||
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false)
|
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false)
|
||||||
&& !(attacker.hasKeyword("Undying") && attacker.getCounters(CounterType.P1P1) == 0)) {
|
&& !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterType.P1P1) == 0)) {
|
||||||
canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature
|
canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature
|
||||||
// see if the defending creature is of higher or lower
|
// see if the defending creature is of higher or lower
|
||||||
// value. We don't want to attack only to lose value
|
// value. We don't want to attack only to lose value
|
||||||
@@ -1029,12 +1159,27 @@ public class AiAttackController {
|
|||||||
if (defender.getSVar("HasCombatEffect").equals("TRUE") || defender.getSVar("HasBlockEffect").equals("TRUE")) {
|
if (defender.getSVar("HasCombatEffect").equals("TRUE") || defender.getSVar("HasBlockEffect").equals("TRUE")) {
|
||||||
canKillAllDangerous = false;
|
canKillAllDangerous = false;
|
||||||
} else {
|
} else {
|
||||||
for (String keyword : defender.getKeywords()) {
|
if (defender.hasKeyword(Keyword.WITHER) || defender.hasKeyword(Keyword.INFECT)
|
||||||
if (keyword.equals("Wither") || keyword.equals("Infect") || keyword.equals("Lifelink")) {
|
|| defender.hasKeyword(Keyword.LIFELINK)) {
|
||||||
|
canKillAllDangerous = false;
|
||||||
|
// there is a creature that can survive an attack from this creature
|
||||||
|
// and combat will have negative effects
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if maybe we are too reckless in adding this attacker
|
||||||
|
if (canKillAllDangerous) {
|
||||||
|
boolean avoidAttackingIntoBlock = ai.getController().isAI()
|
||||||
|
&& ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK);
|
||||||
|
boolean attackerWillDie = defPower >= attacker.getNetToughness();
|
||||||
|
boolean uselessAttack = !hasCombatEffect && !hasAttackEffect;
|
||||||
|
boolean noContributionToAttack = this.attackers.size() <= defenders.size() || attacker.getNetPower() <= 0;
|
||||||
|
|
||||||
|
// We are attacking too recklessly if we can't kill a single blocker and:
|
||||||
|
// - our creature will die for sure (chump attack)
|
||||||
|
// - our attack will not do anything special (no attack/combat effect to proc)
|
||||||
|
// - we can't deal damage to our opponent with sheer number of attackers and/or our attacker's power is 0 or less
|
||||||
|
if (attackerWillDie || (avoidAttackingIntoBlock && (uselessAttack || noContributionToAttack))) {
|
||||||
canKillAllDangerous = false;
|
canKillAllDangerous = false;
|
||||||
break;
|
|
||||||
// there is a creature that can survive an attack from this creature
|
|
||||||
// and combat will have negative effects
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1042,7 +1187,7 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!attacker.hasKeyword("vigilance") && ComputerUtilCard.canBeKilledByRoyalAssassin(ai, attacker)) {
|
if (!attacker.hasKeyword(Keyword.VIGILANCE) && ComputerUtilCard.canBeKilledByRoyalAssassin(ai, attacker)) {
|
||||||
canKillAllDangerous = false;
|
canKillAllDangerous = false;
|
||||||
canBeKilled = true;
|
canBeKilled = true;
|
||||||
canBeKilledByOne = true;
|
canBeKilledByOne = true;
|
||||||
@@ -1128,7 +1273,7 @@ public class AiAttackController {
|
|||||||
// creature would leave the battlefield
|
// creature would leave the battlefield
|
||||||
// no pain in exerting it
|
// no pain in exerting it
|
||||||
shouldExert = true;
|
shouldExert = true;
|
||||||
} else if (c.hasKeyword("Vigilance")) {
|
} else if (c.hasKeyword(Keyword.VIGILANCE)) {
|
||||||
// Free exert - why not?
|
// Free exert - why not?
|
||||||
shouldExert = true;
|
shouldExert = true;
|
||||||
}
|
}
|
||||||
@@ -1147,18 +1292,51 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.setActivatingPlayer(c.getController());
|
sa.setActivatingPlayer(c.getController());
|
||||||
if (CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa).isEmpty()) {
|
List<Card> validTargets = CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa);
|
||||||
|
if (validTargets.isEmpty()) {
|
||||||
|
missTarget = true;
|
||||||
|
break;
|
||||||
|
} else if (sa.isCurse() && CardLists.filter(validTargets,
|
||||||
|
CardPredicates.isControlledByAnyOf(c.getController().getOpponents())).isEmpty()) {
|
||||||
|
// e.g. Ahn-Crop Crasher - the effect is only good when aimed at opponent's creatures
|
||||||
missTarget = true;
|
missTarget = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (missTarget) {
|
if (missTarget) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (random.nextBoolean()) {
|
// A specific AI condition for Exert: if specified on the card, the AI will always
|
||||||
|
// exert creatures that meet this condition
|
||||||
|
if (c.hasSVar("AIExertCondition")) {
|
||||||
|
if (!c.getSVar("AIExertCondition").isEmpty()) {
|
||||||
|
final String needsToExert = c.getSVar("AIExertCondition");
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
String sVar = needsToExert.split(" ")[0];
|
||||||
|
String comparator = needsToExert.split(" ")[1];
|
||||||
|
String compareTo = comparator.substring(2);
|
||||||
|
try {
|
||||||
|
x = Integer.parseInt(sVar);
|
||||||
|
} catch (final NumberFormatException e) {
|
||||||
|
x = CardFactoryUtil.xCount(c, c.getSVar(sVar));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
y = Integer.parseInt(compareTo);
|
||||||
|
} catch (final NumberFormatException e) {
|
||||||
|
y = CardFactoryUtil.xCount(c, c.getSVar(compareTo));
|
||||||
|
}
|
||||||
|
if (Expressions.compare(x, comparator, y)) {
|
||||||
|
shouldExert = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldExert && MyRandom.getRandom().nextBoolean()) {
|
||||||
// TODO Improve when the AI wants to use Exert powers
|
// TODO Improve when the AI wants to use Exert powers
|
||||||
shouldExert = true;
|
shouldExert = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,26 +19,21 @@ package forge.ai;
|
|||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
|
import forge.card.CardStateName;
|
||||||
import forge.game.CardTraitBase;
|
import forge.game.CardTraitBase;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.*;
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardCollectionView;
|
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.card.CardPredicates;
|
|
||||||
import forge.game.card.CounterType;
|
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.MyRandom;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,8 +84,10 @@ public class AiBlockController {
|
|||||||
private List<Card> getSafeBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) {
|
private List<Card> getSafeBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) {
|
||||||
final List<Card> blockers = new ArrayList<>();
|
final List<Card> blockers = new ArrayList<>();
|
||||||
|
|
||||||
|
// We don't check attacker static abilities at this point since the attackers have already attacked and, thus,
|
||||||
|
// their P/T modifiers are active and are counted as a part of getNetPower/getNetToughness
|
||||||
for (final Card b : blockersLeft) {
|
for (final Card b : blockersLeft) {
|
||||||
if (!ComputerUtilCombat.canDestroyBlocker(ai, b, attacker, combat, false)) {
|
if (!ComputerUtilCombat.canDestroyBlocker(ai, b, attacker, combat, false, true)) {
|
||||||
blockers.add(b);
|
blockers.add(b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,8 +99,10 @@ public class AiBlockController {
|
|||||||
private List<Card> getKillingBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) {
|
private List<Card> getKillingBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) {
|
||||||
final List<Card> blockers = new ArrayList<>();
|
final List<Card> blockers = new ArrayList<>();
|
||||||
|
|
||||||
|
// We don't check attacker static abilities at this point since the attackers have already attacked and, thus,
|
||||||
|
// their P/T modifiers are active and are counted as a part of getNetPower/getNetToughness
|
||||||
for (final Card b : blockersLeft) {
|
for (final Card b : blockersLeft) {
|
||||||
if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, b, combat, false)) {
|
if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, b, combat, false, true)) {
|
||||||
blockers.add(b);
|
blockers.add(b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,11 +118,11 @@ public class AiBlockController {
|
|||||||
|
|
||||||
// If I don't have any planeswalkers then sorting doesn't really matter
|
// If I don't have any planeswalkers then sorting doesn't really matter
|
||||||
if (defenders.size() == 1) {
|
if (defenders.size() == 1) {
|
||||||
final CardCollection attackers = combat.getAttackersOf(defenders.get(0));
|
final CardCollection attackers = combat.getAttackersOf(defenders.get(0));
|
||||||
// Begin with the attackers that pose the biggest threat
|
// Begin with the attackers that pose the biggest threat
|
||||||
ComputerUtilCard.sortByEvaluateCreature(attackers);
|
ComputerUtilCard.sortByEvaluateCreature(attackers);
|
||||||
CardLists.sortByPowerDesc(attackers);
|
CardLists.sortByPowerDesc(attackers);
|
||||||
//move cards like Phage the Untouchable to the front
|
//move cards like Phage the Untouchable to the front
|
||||||
Collections.sort(attackers, new Comparator<Card>() {
|
Collections.sort(attackers, new Comparator<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(final Card o1, final Card o2) {
|
public int compare(final Card o1, final Card o2) {
|
||||||
@@ -144,16 +143,16 @@ public class AiBlockController {
|
|||||||
// defend planeswalkers with more loyalty before planeswalkers with less loyalty
|
// defend planeswalkers with more loyalty before planeswalkers with less loyalty
|
||||||
// if planeswalker will be too difficult to defend don't even bother
|
// if planeswalker will be too difficult to defend don't even bother
|
||||||
for (GameEntity defender : defenders) {
|
for (GameEntity defender : defenders) {
|
||||||
if (defender instanceof Card) {
|
if (defender instanceof Card) {
|
||||||
final CardCollection attackers = combat.getAttackersOf(defender);
|
final CardCollection attackers = combat.getAttackersOf(defender);
|
||||||
// Begin with the attackers that pose the biggest threat
|
// Begin with the attackers that pose the biggest threat
|
||||||
CardLists.sortByPowerDesc(attackers);
|
CardLists.sortByPowerDesc(attackers);
|
||||||
for (final Card c : attackers) {
|
for (final Card c : attackers) {
|
||||||
sortedAttackers.add(c);
|
sortedAttackers.add(c);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (defender instanceof Player && defender.equals(ai)) {
|
||||||
firstAttacker = combat.getAttackersOf(defender);
|
firstAttacker = combat.getAttackersOf(defender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bLifeInDanger) {
|
if (bLifeInDanger) {
|
||||||
@@ -182,7 +181,7 @@ public class AiBlockController {
|
|||||||
|
|
||||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|
||||||
|| attacker.hasKeyword("Menace")) {
|
|| attacker.hasKeyword(Keyword.MENACE)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,11 +205,11 @@ public class AiBlockController {
|
|||||||
&& !ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
|
&& !ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
|
||||||
blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers);
|
blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers);
|
||||||
// check whether it's better to block a creature without trample to absorb more damage
|
// check whether it's better to block a creature without trample to absorb more damage
|
||||||
if (attacker.hasKeyword("Trample")) {
|
if (attacker.hasKeyword(Keyword.TRAMPLE)) {
|
||||||
boolean doNotBlock = false;
|
boolean doNotBlock = false;
|
||||||
for (Card other : attackersLeft) {
|
for (Card other : attackersLeft) {
|
||||||
if (other.equals(attacker) || !CombatUtil.canBlock(other, blocker)
|
if (other.equals(attacker) || !CombatUtil.canBlock(other, blocker)
|
||||||
|| other.hasKeyword("Trample")
|
|| other.hasKeyword(Keyword.TRAMPLE)
|
||||||
|| ComputerUtilCombat.attackerHasThreateningAfflict(other, ai)
|
|| ComputerUtilCombat.attackerHasThreateningAfflict(other, ai)
|
||||||
|| ComputerUtilCombat.canDestroyBlocker(ai, blocker, other, combat, false)
|
|| ComputerUtilCombat.canDestroyBlocker(ai, blocker, other, combat, false)
|
||||||
|| other.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
|| other.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
||||||
@@ -233,11 +232,10 @@ public class AiBlockController {
|
|||||||
// 3.Blockers that can destroy the attacker and have an upside when dying
|
// 3.Blockers that can destroy the attacker and have an upside when dying
|
||||||
killingBlockers = getKillingBlockers(combat, attacker, blockers);
|
killingBlockers = getKillingBlockers(combat, attacker, blockers);
|
||||||
for (Card b : killingBlockers) {
|
for (Card b : killingBlockers) {
|
||||||
if ((b.hasKeyword("Undying") && b.getCounters(CounterType.P1P1) == 0)
|
if ((b.hasKeyword(Keyword.UNDYING) && b.getCounters(CounterType.P1P1) == 0) || b.hasSVar("SacMe")
|
||||||
|| b.hasSVar("SacMe")
|
|| (b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterType.TIME) == 1)
|
||||||
|| (b.hasStartOfKeyword("Vanishing") && b.getCounters(CounterType.TIME) == 1)
|
|| (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterType.FADE) == 0)
|
||||||
|| (b.hasStartOfKeyword("Fading") && b.getCounters(CounterType.FADE) == 0)
|
|| b.hasSVar("EndOfTurnLeavePlay")) {
|
||||||
|| b.hasSVar("EndOfTurnLeavePlay")) {
|
|
||||||
blocker = b;
|
blocker = b;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -247,7 +245,7 @@ public class AiBlockController {
|
|||||||
if (b.hasSVar("SacMe") && Integer.parseInt(b.getSVar("SacMe")) > 3) {
|
if (b.hasSVar("SacMe") && Integer.parseInt(b.getSVar("SacMe")) > 3) {
|
||||||
blocker = b;
|
blocker = b;
|
||||||
if (!ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)) {
|
if (!ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)) {
|
||||||
blockedButUnkilled.add(attacker);
|
blockedButUnkilled.add(attacker);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -295,8 +293,7 @@ public class AiBlockController {
|
|||||||
|
|
||||||
// 6. Blockers that don't survive until the next turn anyway
|
// 6. Blockers that don't survive until the next turn anyway
|
||||||
for (final Card attacker : attackersLeft) {
|
for (final Card attacker : attackersLeft) {
|
||||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)
|
||||||
|| attacker.hasKeyword("Menace")
|
|
||||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -306,12 +303,12 @@ public class AiBlockController {
|
|||||||
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||||
|
|
||||||
for (Card b : blockers) {
|
for (Card b : blockers) {
|
||||||
if ((b.hasStartOfKeyword("Vanishing") && b.getCounters(CounterType.TIME) == 1)
|
if ((b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterType.TIME) == 1)
|
||||||
|| (b.hasStartOfKeyword("Fading") && b.getCounters(CounterType.FADE) == 0)
|
|| (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterType.FADE) == 0)
|
||||||
|| b.hasSVar("EndOfTurnLeavePlay")) {
|
|| b.hasSVar("EndOfTurnLeavePlay")) {
|
||||||
blocker = b;
|
blocker = b;
|
||||||
if (!ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)) {
|
if (!ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)) {
|
||||||
blockedButUnkilled.add(attacker);
|
blockedButUnkilled.add(attacker);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -349,9 +346,9 @@ public class AiBlockController {
|
|||||||
final List<Card> firstStrikeBlockers = new ArrayList<>();
|
final List<Card> firstStrikeBlockers = new ArrayList<>();
|
||||||
final List<Card> blockGang = new ArrayList<>();
|
final List<Card> blockGang = new ArrayList<>();
|
||||||
for (Card blocker : blockers) {
|
for (Card blocker : blockers) {
|
||||||
if (ComputerUtilCombat.canDestroyBlockerBeforeFirstStrike(blocker, attacker, false)) {
|
if (ComputerUtilCombat.canDestroyBlockerBeforeFirstStrike(blocker, attacker, false)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (blocker.hasFirstStrike() || blocker.hasDoubleStrike()) {
|
if (blocker.hasFirstStrike() || blocker.hasDoubleStrike()) {
|
||||||
firstStrikeBlockers.add(blocker);
|
firstStrikeBlockers.add(blocker);
|
||||||
}
|
}
|
||||||
@@ -417,7 +414,8 @@ public class AiBlockController {
|
|||||||
&& !ComputerUtilCombat.dealsFirstStrikeDamage(c, false, combat)) {
|
&& !ComputerUtilCombat.dealsFirstStrikeDamage(c, false, combat)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return lifeInDanger || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker);
|
final boolean randomTrade = wouldLikeToRandomlyTrade(attacker, c, combat);
|
||||||
|
return lifeInDanger || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker) || randomTrade;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (usableBlockers.size() < 2) {
|
if (usableBlockers.size() < 2) {
|
||||||
@@ -445,9 +443,9 @@ public class AiBlockController {
|
|||||||
// The attacker will be killed
|
// The attacker will be killed
|
||||||
&& (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage()
|
&& (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage()
|
||||||
// only one blocker can be killed
|
// only one blocker can be killed
|
||||||
|| currentValue + addedValue - 50 <= evalAttackerValue
|
|| currentValue + addedValue - 50 <= evalAttackerValue
|
||||||
// or attacker is worth more
|
// or attacker is worth more
|
||||||
|| (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)))
|
|| (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)))
|
||||||
// or life is in danger
|
// or life is in danger
|
||||||
&& CombatUtil.canBlock(attacker, blocker, combat)) {
|
&& CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||||
// this is needed for attackers that can't be blocked by
|
// this is needed for attackers that can't be blocked by
|
||||||
@@ -497,7 +495,7 @@ public class AiBlockController {
|
|||||||
&& !(damageNeeded > currentDamage + additionalDamage2 + additionalDamage3)
|
&& !(damageNeeded > currentDamage + additionalDamage2 + additionalDamage3)
|
||||||
// The attacker will be killed
|
// The attacker will be killed
|
||||||
&& ((absorbedDamage2 + absorbedDamage > netCombatDamage && absorbedDamage3 + absorbedDamage > netCombatDamage
|
&& ((absorbedDamage2 + absorbedDamage > netCombatDamage && absorbedDamage3 + absorbedDamage > netCombatDamage
|
||||||
&& absorbedDamage3 + absorbedDamage2 > netCombatDamage)
|
&& absorbedDamage3 + absorbedDamage2 > netCombatDamage)
|
||||||
// only one blocker can be killed
|
// only one blocker can be killed
|
||||||
|| currentValue + addedValue2 + addedValue3 - 50 <= evalAttackerValue
|
|| currentValue + addedValue2 + addedValue3 - 50 <= evalAttackerValue
|
||||||
// or attacker is worth more
|
// or attacker is worth more
|
||||||
@@ -526,7 +524,56 @@ public class AiBlockController {
|
|||||||
attackersLeft = (new ArrayList<>(currentAttackers));
|
attackersLeft = (new ArrayList<>(currentAttackers));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void makeGangNonLethalBlocks(final Combat combat) {
|
||||||
|
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||||
|
List<Card> blockers;
|
||||||
|
|
||||||
|
// Try to block a Menace attacker with two blockers, neither of which will die
|
||||||
|
for (final Card attacker : attackersLeft) {
|
||||||
|
if (!attacker.hasKeyword(Keyword.MENACE) && !attacker.hasStartOfKeyword("CantBeBlockedByAmount LT2")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
|
||||||
|
List<Card> usableBlockers;
|
||||||
|
final List<Card> blockGang = new ArrayList<>();
|
||||||
|
int absorbedDamage; // The amount of damage needed to kill the first blocker
|
||||||
|
|
||||||
|
usableBlockers = CardLists.filter(blockers, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
return c.getNetToughness() > attacker.getNetCombatDamage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (usableBlockers.size() < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Card leader = ComputerUtilCard.getWorstCreatureAI(usableBlockers);
|
||||||
|
blockGang.add(leader);
|
||||||
|
usableBlockers.remove(leader);
|
||||||
|
absorbedDamage = ComputerUtilCombat.getEnoughDamageToKill(leader, attacker.getNetCombatDamage(), attacker, true);
|
||||||
|
|
||||||
|
// consider a double block
|
||||||
|
for (final Card blocker : usableBlockers) {
|
||||||
|
final int absorbedDamage2 = ComputerUtilCombat.getEnoughDamageToKill(blocker, attacker.getNetCombatDamage(), attacker, true);
|
||||||
|
// only do it if neither blocking creature will die
|
||||||
|
if (absorbedDamage > attacker.getNetCombatDamage() && absorbedDamage2 > attacker.getNetCombatDamage()) {
|
||||||
|
currentAttackers.remove(attacker);
|
||||||
|
combat.addBlocker(attacker, blocker);
|
||||||
|
if (CombatUtil.canBlock(attacker, leader, combat)) {
|
||||||
|
combat.addBlocker(attacker, leader);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attackersLeft = (new ArrayList<>(currentAttackers));
|
||||||
|
}
|
||||||
|
|
||||||
// Bad Trade Blocks (should only be made if life is in danger)
|
// Bad Trade Blocks (should only be made if life is in danger)
|
||||||
|
// Random Trade Blocks (performed randomly if enabled in profile and only when in favorable conditions)
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* makeTradeBlocks.
|
* makeTradeBlocks.
|
||||||
@@ -542,20 +589,33 @@ public class AiBlockController {
|
|||||||
for (final Card attacker : attackersLeft) {
|
for (final Card attacker : attackersLeft) {
|
||||||
|
|
||||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||||
|| attacker.hasKeyword("Menace")
|
|| attacker.hasKeyword(Keyword.MENACE)
|
||||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
List<Card> possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
List<Card> possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||||
killingBlockers = getKillingBlockers(combat, attacker, possibleBlockers);
|
killingBlockers = getKillingBlockers(combat, attacker, possibleBlockers);
|
||||||
if (!killingBlockers.isEmpty() && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
|
||||||
if (ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
|
if (!killingBlockers.isEmpty()) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final Card blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
|
final Card blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
|
||||||
combat.addBlocker(attacker, blocker);
|
boolean doTrade = false;
|
||||||
currentAttackers.remove(attacker);
|
|
||||||
|
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
|
// Always trade when life in danger
|
||||||
|
doTrade = true;
|
||||||
|
} else {
|
||||||
|
// Randomly trade creatures with lower power and [hopefully] worse abilities, if enabled in profile
|
||||||
|
doTrade = wouldLikeToRandomlyTrade(attacker, blocker, combat);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doTrade) {
|
||||||
|
combat.addBlocker(attacker, blocker);
|
||||||
|
currentAttackers.remove(attacker);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
attackersLeft = (new ArrayList<>(currentAttackers));
|
attackersLeft = (new ArrayList<>(currentAttackers));
|
||||||
@@ -584,7 +644,7 @@ public class AiBlockController {
|
|||||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||||
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|
||||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|
||||||
|| attacker.hasKeyword("Menace")
|
|| attacker.hasKeyword(Keyword.MENACE)
|
||||||
|| ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
|
|| ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
|
||||||
attackers.remove(0);
|
attackers.remove(0);
|
||||||
makeChumpBlocks(combat, attackers);
|
makeChumpBlocks(combat, attackers);
|
||||||
@@ -596,7 +656,7 @@ public class AiBlockController {
|
|||||||
final Card blocker = ComputerUtilCard.getWorstCreatureAI(chumpBlockers);
|
final Card blocker = ComputerUtilCard.getWorstCreatureAI(chumpBlockers);
|
||||||
|
|
||||||
// check if it's better to block a creature with lower power and without trample
|
// check if it's better to block a creature with lower power and without trample
|
||||||
if (attacker.hasKeyword("Trample")) {
|
if (attacker.hasKeyword(Keyword.TRAMPLE)) {
|
||||||
final int damageAbsorbed = blocker.getLethalDamage();
|
final int damageAbsorbed = blocker.getLethalDamage();
|
||||||
if (attacker.getNetCombatDamage() > damageAbsorbed) {
|
if (attacker.getNetCombatDamage() > damageAbsorbed) {
|
||||||
for (Card other : attackers) {
|
for (Card other : attackers) {
|
||||||
@@ -604,7 +664,7 @@ public class AiBlockController {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (other.getNetCombatDamage() >= damageAbsorbed
|
if (other.getNetCombatDamage() >= damageAbsorbed
|
||||||
&& !other.hasKeyword("Trample")
|
&& !other.hasKeyword(Keyword.TRAMPLE)
|
||||||
&& !other.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|
&& !other.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|
||||||
&& !ComputerUtilCombat.attackerHasThreateningAfflict(other, ai)
|
&& !ComputerUtilCombat.attackerHasThreateningAfflict(other, ai)
|
||||||
&& CombatUtil.canBlock(other, blocker, combat)) {
|
&& CombatUtil.canBlock(other, blocker, combat)) {
|
||||||
@@ -635,7 +695,7 @@ public class AiBlockController {
|
|||||||
for (final Card attacker : currentAttackers) {
|
for (final Card attacker : currentAttackers) {
|
||||||
|
|
||||||
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||||
&& !attacker.hasKeyword("Menace")
|
&& !attacker.hasKeyword(Keyword.MENACE)
|
||||||
&& !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
&& !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -668,7 +728,7 @@ public class AiBlockController {
|
|||||||
|
|
||||||
List<Card> chumpBlockers;
|
List<Card> chumpBlockers;
|
||||||
|
|
||||||
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, "Trample");
|
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE);
|
||||||
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
|
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
|
||||||
|
|
||||||
// TODO - should check here for a "rampage-like" trigger that replaced
|
// TODO - should check here for a "rampage-like" trigger that replaced
|
||||||
@@ -677,7 +737,7 @@ public class AiBlockController {
|
|||||||
|
|
||||||
for (final Card attacker : tramplingAttackers) {
|
for (final Card attacker : tramplingAttackers) {
|
||||||
|
|
||||||
if (((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("Menace")) && !combat.isBlocked(attacker))
|
if (((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) && !combat.isBlocked(attacker))
|
||||||
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|
||||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||||
continue;
|
continue;
|
||||||
@@ -714,29 +774,29 @@ public class AiBlockController {
|
|||||||
// Try to use safe blockers first
|
// Try to use safe blockers first
|
||||||
if (blockers.size() > 0) {
|
if (blockers.size() > 0) {
|
||||||
safeBlockers = getSafeBlockers(combat, attacker, blockers);
|
safeBlockers = getSafeBlockers(combat, attacker, blockers);
|
||||||
for (final Card blocker : safeBlockers) {
|
for (final Card blocker : safeBlockers) {
|
||||||
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
||||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
||||||
// Add an additional blocker if the current blockers are not
|
// Add an additional blocker if the current blockers are not
|
||||||
// enough and the new one would deal additional damage
|
// enough and the new one would deal additional damage
|
||||||
if (damageNeeded > ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker))
|
if (damageNeeded > ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker))
|
||||||
&& ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker) > 0
|
&& ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker) > 0
|
||||||
&& CombatUtil.canBlock(attacker, blocker, combat)) {
|
&& CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||||
combat.addBlocker(attacker, blocker);
|
combat.addBlocker(attacker, blocker);
|
||||||
}
|
}
|
||||||
blockers.remove(blocker); // Don't check them again next
|
blockers.remove(blocker); // Don't check them again next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// don't try to kill what can't be killed
|
// don't try to kill what can't be killed
|
||||||
if (attacker.hasKeyword("indestructible") || ComputerUtil.canRegenerate(ai, attacker)) {
|
if (attacker.hasKeyword(Keyword.INDESTRUCTIBLE) || ComputerUtil.canRegenerate(ai, attacker)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to add blockers that could be destroyed, but are worth less than the attacker
|
// Try to add blockers that could be destroyed, but are worth less than the attacker
|
||||||
// Don't use blockers without First Strike or Double Strike if attacker has it
|
// Don't use blockers without First Strike or Double Strike if attacker has it
|
||||||
if (ComputerUtilCombat.dealsFirstStrikeDamage(attacker, false, combat)) {
|
if (ComputerUtilCombat.dealsFirstStrikeDamage(attacker, false, combat)) {
|
||||||
safeBlockers = CardLists.getKeyword(blockers, "First Strike");
|
safeBlockers = CardLists.getKeyword(blockers, Keyword.FIRST_STRIKE);
|
||||||
safeBlockers.addAll(CardLists.getKeyword(blockers, "Double Strike"));
|
safeBlockers.addAll(CardLists.getKeyword(blockers, Keyword.DOUBLE_STRIKE));
|
||||||
} else {
|
} else {
|
||||||
safeBlockers = new ArrayList<>(blockers);
|
safeBlockers = new ArrayList<>(blockers);
|
||||||
}
|
}
|
||||||
@@ -760,6 +820,105 @@ public class AiBlockController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void makeChumpBlocksToSavePW(Combat combat) {
|
||||||
|
if (ComputerUtilCombat.lifeInDanger(ai, combat) || ai.getLife() <= ai.getStartingLife() / 5) {
|
||||||
|
// most likely not worth trying to protect planeswalkers when at threateningly low life or in
|
||||||
|
// dangerous combat which threatens lethal or severe damage to face
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||||
|
final int evalThresholdToken = aic.getIntProperty(AiProps.THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER);
|
||||||
|
final int evalThresholdNonToken = aic.getIntProperty(AiProps.THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER);
|
||||||
|
final boolean onlyIfLethal = aic.getBooleanProperty(AiProps.CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL);
|
||||||
|
|
||||||
|
if (evalThresholdToken > 0 || evalThresholdNonToken > 0) {
|
||||||
|
// detect how much damage is threatened to each of the planeswalkers, see which ones would be
|
||||||
|
// worth protecting according to the AI profile properties
|
||||||
|
CardCollection threatenedPWs = new CardCollection();
|
||||||
|
for (final Card attacker : attackers) {
|
||||||
|
GameEntity def = combat.getDefenderByAttacker(attacker);
|
||||||
|
if (def instanceof Card) {
|
||||||
|
if (!onlyIfLethal) {
|
||||||
|
threatenedPWs.add((Card) def);
|
||||||
|
} else {
|
||||||
|
int damageToPW = 0;
|
||||||
|
for (final Card pwatkr : combat.getAttackersOf(def)) {
|
||||||
|
if (!combat.isBlocked(pwatkr)) {
|
||||||
|
damageToPW += ComputerUtilCombat.predictDamageTo((Card) def, pwatkr.getNetCombatDamage(), pwatkr, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((!onlyIfLethal && damageToPW > 0) || damageToPW >= ((Card) def).getCounters(CounterType.LOYALTY)) {
|
||||||
|
threatenedPWs.add((Card) def);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CardCollection pwsWithChumpBlocks = new CardCollection();
|
||||||
|
CardCollection chosenChumpBlockers = new CardCollection();
|
||||||
|
CardCollection chumpPWDefenders = CardLists.filter(new CardCollection(this.blockersLeft), new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(Card card) {
|
||||||
|
return ComputerUtilCard.evaluateCreature(card) <= (card.isToken() ? evalThresholdToken
|
||||||
|
: evalThresholdNonToken);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
CardLists.sortByPowerAsc(chumpPWDefenders);
|
||||||
|
if (!chumpPWDefenders.isEmpty()) {
|
||||||
|
for (final Card attacker : attackers) {
|
||||||
|
GameEntity def = combat.getDefenderByAttacker(attacker);
|
||||||
|
if (def instanceof Card && threatenedPWs.contains((Card) def)) {
|
||||||
|
if (attacker.hasKeyword(Keyword.TRAMPLE)) {
|
||||||
|
// don't bother trying to chump a trampling creature
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!combat.getBlockers(attacker).isEmpty()) {
|
||||||
|
// already blocked by something, no need to chump
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Card blockerDecided = null;
|
||||||
|
for (final Card blocker : chumpPWDefenders) {
|
||||||
|
if (CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||||
|
combat.addBlocker(attacker, blocker);
|
||||||
|
pwsWithChumpBlocks.add((Card) combat.getDefenderByAttacker(attacker));
|
||||||
|
chosenChumpBlockers.add(blocker);
|
||||||
|
blockerDecided = blocker;
|
||||||
|
blockersLeft.remove(blocker);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chumpPWDefenders.remove(blockerDecided);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check to see if we managed to cover all the blockers of the planeswalker; if not, bail
|
||||||
|
for (final Card pw : pwsWithChumpBlocks) {
|
||||||
|
CardCollection pwAttackers = combat.getAttackersOf(pw);
|
||||||
|
CardCollection pwDefenders = new CardCollection();
|
||||||
|
boolean isFullyBlocked = true;
|
||||||
|
if (!pwAttackers.isEmpty()) {
|
||||||
|
int damageToPW = 0;
|
||||||
|
for (Card pwAtk : pwAttackers) {
|
||||||
|
if (!combat.getBlockers(pwAtk).isEmpty()) {
|
||||||
|
pwDefenders.addAll(combat.getBlockers(pwAtk));
|
||||||
|
} else {
|
||||||
|
isFullyBlocked = false;
|
||||||
|
damageToPW += ComputerUtilCombat.predictDamageTo((Card) pw, pwAtk.getNetCombatDamage(), pwAtk, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isFullyBlocked && damageToPW >= pw.getCounters(CounterType.LOYALTY)) {
|
||||||
|
for (Card chump : pwDefenders) {
|
||||||
|
if (chosenChumpBlockers.contains(chump)) {
|
||||||
|
combat.removeFromCombat(chump);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void clearBlockers(final Combat combat, final List<Card> possibleBlockers) {
|
private void clearBlockers(final Combat combat, final List<Card> possibleBlockers) {
|
||||||
|
|
||||||
final List<Card> oldBlockers = combat.getAllBlockers();
|
final List<Card> oldBlockers = combat.getAllBlockers();
|
||||||
@@ -824,8 +983,8 @@ public class AiBlockController {
|
|||||||
List<Card> chumpBlockers;
|
List<Card> chumpBlockers;
|
||||||
|
|
||||||
diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade
|
diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade
|
||||||
if (ai.getController().isAI() && diff > 0 && ((PlayerControllerAi) ai.getController()).getAi().getProperty(AiProps.PLAY_AGGRO).equals("true")) {
|
if (ai.getController().isAI() && diff > 0 && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.PLAY_AGGRO)) {
|
||||||
diff = 0;
|
diff = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove all attackers that can't be blocked anyway
|
// remove all attackers that can't be blocked anyway
|
||||||
@@ -855,78 +1014,85 @@ public class AiBlockController {
|
|||||||
|
|
||||||
// When the AI holds some Fog effect, don't bother about lifeInDanger
|
// When the AI holds some Fog effect, don't bother about lifeInDanger
|
||||||
if (!ComputerUtil.hasAFogEffect(ai)) {
|
if (!ComputerUtil.hasAFogEffect(ai)) {
|
||||||
lifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat);
|
lifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat);
|
||||||
if (lifeInDanger) {
|
makeTradeBlocks(combat); // choose necessary trade blocks
|
||||||
makeTradeBlocks(combat); // choose necessary trade blocks
|
|
||||||
}
|
// if life is still in danger
|
||||||
// if life is still in danger
|
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
makeChumpBlocks(combat); // choose necessary chump blocks
|
||||||
makeChumpBlocks(combat); // choose necessary chump blocks
|
} else {
|
||||||
} else {
|
lifeInDanger = false;
|
||||||
lifeInDanger = false;
|
|
||||||
}
|
}
|
||||||
// if life is still in danger
|
// if life is still in danger
|
||||||
// Reinforce blockers blocking attackers with trample if life is still
|
// Reinforce blockers blocking attackers with trample if life is
|
||||||
// in danger
|
// still
|
||||||
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
// in danger
|
||||||
reinforceBlockersAgainstTrample(combat);
|
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
} else {
|
reinforceBlockersAgainstTrample(combat);
|
||||||
lifeInDanger = false;
|
} else {
|
||||||
|
lifeInDanger = false;
|
||||||
|
}
|
||||||
|
// Support blockers not destroying the attacker with more blockers
|
||||||
|
// to
|
||||||
|
// try to kill the attacker
|
||||||
|
if (!lifeInDanger) {
|
||||||
|
reinforceBlockersToKill(combat);
|
||||||
}
|
}
|
||||||
// Support blockers not destroying the attacker with more blockers to
|
|
||||||
// try to kill the attacker
|
|
||||||
if (!lifeInDanger) {
|
|
||||||
reinforceBlockersToKill(combat);
|
|
||||||
}
|
|
||||||
|
|
||||||
// == 2. If the AI life would still be in danger make a safer approach ==
|
// == 2. If the AI life would still be in danger make a safer
|
||||||
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
// approach ==
|
||||||
clearBlockers(combat, possibleBlockers); // reset every block assignment
|
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
makeTradeBlocks(combat); // choose necessary trade blocks
|
clearBlockers(combat, possibleBlockers); // reset every block
|
||||||
// if life is in danger
|
// assignment
|
||||||
makeGoodBlocks(combat);
|
makeTradeBlocks(combat); // choose necessary trade blocks
|
||||||
// choose necessary chump blocks if life is still in danger
|
// if life is in danger
|
||||||
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
makeGoodBlocks(combat);
|
||||||
makeChumpBlocks(combat);
|
// choose necessary chump blocks if life is still in danger
|
||||||
} else {
|
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
lifeInDanger = false;
|
makeChumpBlocks(combat);
|
||||||
}
|
} else {
|
||||||
// Reinforce blockers blocking attackers with trample if life is
|
lifeInDanger = false;
|
||||||
// still in danger
|
}
|
||||||
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
// Reinforce blockers blocking attackers with trample if life is
|
||||||
reinforceBlockersAgainstTrample(combat);
|
// still in danger
|
||||||
} else {
|
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
lifeInDanger = false;
|
reinforceBlockersAgainstTrample(combat);
|
||||||
}
|
} else {
|
||||||
makeGangBlocks(combat);
|
lifeInDanger = false;
|
||||||
reinforceBlockersToKill(combat);
|
}
|
||||||
}
|
makeGangBlocks(combat);
|
||||||
|
reinforceBlockersToKill(combat);
|
||||||
|
}
|
||||||
|
|
||||||
// == 3. If the AI life would be in serious danger make an even safer approach ==
|
// == 3. If the AI life would be in serious danger make an even
|
||||||
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
// safer approach ==
|
||||||
clearBlockers(combat, possibleBlockers); // reset every block assignment
|
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
||||||
makeChumpBlocks(combat); // choose chump blocks
|
clearBlockers(combat, possibleBlockers); // reset every block
|
||||||
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
// assignment
|
||||||
makeTradeBlocks(combat); // choose necessary trade
|
makeChumpBlocks(combat); // choose chump blocks
|
||||||
}
|
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
|
makeTradeBlocks(combat); // choose necessary trade
|
||||||
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
makeGoodBlocks(combat);
|
makeGoodBlocks(combat);
|
||||||
}
|
}
|
||||||
// Reinforce blockers blocking attackers with trample if life is
|
// Reinforce blockers blocking attackers with trample if life is
|
||||||
// still in danger
|
// still in danger
|
||||||
else {
|
else {
|
||||||
reinforceBlockersAgainstTrample(combat);
|
reinforceBlockersAgainstTrample(combat);
|
||||||
}
|
}
|
||||||
makeGangBlocks(combat);
|
makeGangBlocks(combat);
|
||||||
// Support blockers not destroying the attacker with more blockers
|
// Support blockers not destroying the attacker with more
|
||||||
// to try to kill the attacker
|
// blockers
|
||||||
reinforceBlockersToKill(combat);
|
// to try to kill the attacker
|
||||||
}
|
reinforceBlockersToKill(combat);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// assign blockers that have to block
|
// assign blockers that have to block
|
||||||
chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each turn if able.");
|
chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each turn if able.");
|
||||||
|
chumpBlockers.addAll(CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."));
|
||||||
// if an attacker with lure attacks - all that can block
|
// if an attacker with lure attacks - all that can block
|
||||||
for (final Card blocker : blockersLeft) {
|
for (final Card blocker : blockersLeft) {
|
||||||
if (CombatUtil.mustBlockAnAttacker(blocker, combat)) {
|
if (CombatUtil.mustBlockAnAttacker(blocker, combat)) {
|
||||||
@@ -940,7 +1106,8 @@ public class AiBlockController {
|
|||||||
for (final Card blocker : blockers) {
|
for (final Card blocker : blockers) {
|
||||||
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
|
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
|
||||||
&& (CombatUtil.mustBlockAnAttacker(blocker, combat)
|
&& (CombatUtil.mustBlockAnAttacker(blocker, combat)
|
||||||
|| blocker.hasKeyword("CARDNAME blocks each turn if able."))) {
|
|| blocker.hasKeyword("CARDNAME blocks each turn if able.")
|
||||||
|
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
|
||||||
combat.addBlocker(attacker, blocker);
|
combat.addBlocker(attacker, blocker);
|
||||||
if (blocker.getMustBlockCards() != null) {
|
if (blocker.getMustBlockCards() != null) {
|
||||||
int mustBlockAmt = blocker.getMustBlockCards().size();
|
int mustBlockAmt = blocker.getMustBlockCards().size();
|
||||||
@@ -956,6 +1123,18 @@ public class AiBlockController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// check to see if it's possible to defend a Planeswalker under attack with a chump block,
|
||||||
|
// unless life is low enough to be more worried about saving preserving the life total
|
||||||
|
if (ai.getController().isAI() && !ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
|
makeChumpBlocksToSavePW(combat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are still blockers left, see if it's possible to block Menace creatures with
|
||||||
|
// non-lethal blockers that won't kill the attacker but won't die to it as well
|
||||||
|
makeGangNonLethalBlocks(combat);
|
||||||
|
|
||||||
//Check for validity of blocks in case something slipped through
|
//Check for validity of blocks in case something slipped through
|
||||||
for (Card attacker : attackers) {
|
for (Card attacker : attackers) {
|
||||||
if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, combat.getBlockers(attacker).size(), combat)) {
|
if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, combat.getBlockers(attacker).size(), combat)) {
|
||||||
@@ -994,10 +1173,11 @@ public class AiBlockController {
|
|||||||
* Orders a blocker that put onto the battlefield blocking. Depends heavily
|
* Orders a blocker that put onto the battlefield blocking. Depends heavily
|
||||||
* on the implementation of orderBlockers().
|
* on the implementation of orderBlockers().
|
||||||
*/
|
*/
|
||||||
public static CardCollection orderBlocker(final Card attacker, final Card blocker, final CardCollection oldBlockers) {
|
public static CardCollection orderBlocker(final Card attacker, final Card blocker,
|
||||||
// add blocker to existing ordering
|
final CardCollection oldBlockers) {
|
||||||
// sort by evaluate, then insert it appropriately
|
// add blocker to existing ordering
|
||||||
// relies on current implementation of orderBlockers()
|
// sort by evaluate, then insert it appropriately
|
||||||
|
// relies on current implementation of orderBlockers()
|
||||||
final CardCollection allBlockers = new CardCollection(oldBlockers);
|
final CardCollection allBlockers = new CardCollection(oldBlockers);
|
||||||
allBlockers.add(blocker);
|
allBlockers.add(blocker);
|
||||||
ComputerUtilCard.sortByEvaluateCreature(allBlockers);
|
ComputerUtilCard.sortByEvaluateCreature(allBlockers);
|
||||||
@@ -1009,24 +1189,28 @@ public class AiBlockController {
|
|||||||
boolean newBlockerIsAdded = false;
|
boolean newBlockerIsAdded = false;
|
||||||
// The new blocker comes right after this one
|
// The new blocker comes right after this one
|
||||||
final Card newBlockerRightAfter = (newBlockerIndex == 0 ? null : allBlockers.get(newBlockerIndex - 1));
|
final Card newBlockerRightAfter = (newBlockerIndex == 0 ? null : allBlockers.get(newBlockerIndex - 1));
|
||||||
if (newBlockerRightAfter == null && damage >= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
|
if (newBlockerRightAfter == null
|
||||||
result.add(blocker);
|
&& damage >= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
|
||||||
newBlockerIsAdded = true;
|
result.add(blocker);
|
||||||
|
newBlockerIsAdded = true;
|
||||||
}
|
}
|
||||||
// Don't bother to keep damage up-to-date after the new blocker is added, as we can't modify the order of the other cards anyway
|
// Don't bother to keep damage up-to-date after the new blocker is
|
||||||
|
// added, as we can't modify the order of the other cards anyway
|
||||||
for (final Card c : oldBlockers) {
|
for (final Card c : oldBlockers) {
|
||||||
final int lethal = ComputerUtilCombat.getEnoughDamageToKill(c, damage, attacker, true);
|
final int lethal = ComputerUtilCombat.getEnoughDamageToKill(c, damage, attacker, true);
|
||||||
damage -= lethal;
|
damage -= lethal;
|
||||||
result.add(c);
|
result.add(c);
|
||||||
if (!newBlockerIsAdded && c == newBlockerRightAfter && damage <= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
|
if (!newBlockerIsAdded && c == newBlockerRightAfter
|
||||||
// If blocker is right after this card in priority and we have sufficient damage to kill it, add it here
|
&& damage <= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
|
||||||
result.add(blocker);
|
// If blocker is right after this card in priority and we have
|
||||||
newBlockerIsAdded = true;
|
// sufficient damage to kill it, add it here
|
||||||
}
|
result.add(blocker);
|
||||||
|
newBlockerIsAdded = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// We don't have sufficient damage, just add it at the end!
|
// We don't have sufficient damage, just add it at the end!
|
||||||
if (!newBlockerIsAdded) {
|
if (!newBlockerIsAdded) {
|
||||||
result.add(blocker);
|
result.add(blocker);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -1056,4 +1240,91 @@ public class AiBlockController {
|
|||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean wouldLikeToRandomlyTrade(Card attacker, Card blocker, Combat combat) {
|
||||||
|
// Determines if the AI would like to randomly trade its blocker for the attacker in given combat
|
||||||
|
boolean enableRandomTrades = false;
|
||||||
|
boolean randomTradeIfBehindOnBoard = false;
|
||||||
|
boolean randomTradeIfCreatInHand = false;
|
||||||
|
int chanceModForEmbalm = 0;
|
||||||
|
int chanceToTradeToSaveWalker = 0;
|
||||||
|
int chanceToTradeDownToSaveWalker = 0;
|
||||||
|
int minRandomTradeChance = 0;
|
||||||
|
int maxRandomTradeChance = 0;
|
||||||
|
int maxCreatDiff = 0;
|
||||||
|
int maxCreatDiffWithRepl = 0;
|
||||||
|
int aiCreatureCount = 0;
|
||||||
|
int oppCreatureCount = 0;
|
||||||
|
if (ai.getController().isAI()) {
|
||||||
|
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||||
|
enableRandomTrades = aic.getBooleanProperty(AiProps.ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK);
|
||||||
|
randomTradeIfBehindOnBoard = aic.getBooleanProperty(AiProps.RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS);
|
||||||
|
randomTradeIfCreatInHand = aic.getBooleanProperty(AiProps.ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT);
|
||||||
|
minRandomTradeChance = aic.getIntProperty(AiProps.MIN_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK);
|
||||||
|
maxRandomTradeChance = aic.getIntProperty(AiProps.MAX_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK);
|
||||||
|
chanceModForEmbalm = aic.getIntProperty(AiProps.CHANCE_DECREASE_TO_TRADE_VS_EMBALM);
|
||||||
|
maxCreatDiff = aic.getIntProperty(AiProps.MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE);
|
||||||
|
maxCreatDiffWithRepl = aic.getIntProperty(AiProps.MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE_WITH_REPL);
|
||||||
|
chanceToTradeToSaveWalker = aic.getIntProperty(AiProps.CHANCE_TO_TRADE_TO_SAVE_PLANESWALKER);
|
||||||
|
chanceToTradeDownToSaveWalker = aic.getIntProperty(AiProps.CHANCE_TO_TRADE_DOWN_TO_SAVE_PLANESWALKER);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enableRandomTrades) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
aiCreatureCount = ComputerUtil.countUsefulCreatures(ai);
|
||||||
|
|
||||||
|
if (!attackersLeft.isEmpty()) {
|
||||||
|
oppCreatureCount = ComputerUtil.countUsefulCreatures(attackersLeft.get(0).getController());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attacker.getOwner().equals(ai) && "6".equals(attacker.getSVar("SacMe"))) {
|
||||||
|
// Temporarily controlled object - don't trade with it
|
||||||
|
// TODO: find a more reliable way to figure out that control will be reestablished next turn
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int numSteps = ai.getStartingLife() - 5; // e.g. 15 steps between 5 life and 20 life
|
||||||
|
float chanceStep = (maxRandomTradeChance - minRandomTradeChance) / numSteps;
|
||||||
|
int chance = (int)Math.max(minRandomTradeChance, (maxRandomTradeChance - (Math.max(5, ai.getLife() - 5)) * chanceStep));
|
||||||
|
if (chance > maxRandomTradeChance) {
|
||||||
|
chance = maxRandomTradeChance;
|
||||||
|
}
|
||||||
|
|
||||||
|
int evalAtk = ComputerUtilCard.evaluateCreature(attacker, true, false);
|
||||||
|
int evalBlk = ComputerUtilCard.evaluateCreature(blocker, true, false);
|
||||||
|
boolean atkEmbalm = (attacker.hasStartOfKeyword("Embalm") || attacker.hasStartOfKeyword("Eternalize")) && !attacker.isToken();
|
||||||
|
boolean blkEmbalm = (blocker.hasStartOfKeyword("Embalm") || blocker.hasStartOfKeyword("Eternalize")) && !blocker.isToken();
|
||||||
|
|
||||||
|
if (atkEmbalm && !blkEmbalm) {
|
||||||
|
// The opponent will eventually get his creature back, while the AI won't
|
||||||
|
chance = Math.max(0, chance - chanceModForEmbalm);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocker.isFaceDown() && blocker.getState(CardStateName.Original).getType().isCreature()) {
|
||||||
|
// if the blocker is a face-down creature (e.g. cast via Morph, Manifest), evaluate it
|
||||||
|
// in relation to the original state, not to the Morph state
|
||||||
|
evalBlk = ComputerUtilCard.evaluateCreature(Card.fromPaperCard(blocker.getPaperCard(), ai), false, true);
|
||||||
|
}
|
||||||
|
int chanceToSavePW = chanceToTradeDownToSaveWalker > 0 && evalAtk + 1 < evalBlk ? chanceToTradeDownToSaveWalker : chanceToTradeToSaveWalker;
|
||||||
|
boolean powerParityOrHigher = blocker.getNetPower() <= attacker.getNetPower();
|
||||||
|
boolean creatureParityOrAllowedDiff = aiCreatureCount
|
||||||
|
+ (randomTradeIfBehindOnBoard ? maxCreatDiff : 0) >= oppCreatureCount;
|
||||||
|
boolean wantToTradeWithCreatInHand = randomTradeIfCreatInHand
|
||||||
|
&& !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.CREATURES).isEmpty()
|
||||||
|
&& aiCreatureCount + maxCreatDiffWithRepl >= oppCreatureCount;
|
||||||
|
boolean wantToSavePlaneswalker = MyRandom.percentTrue(chanceToSavePW)
|
||||||
|
&& combat.getDefenderByAttacker(attacker) instanceof Card
|
||||||
|
&& ((Card) combat.getDefenderByAttacker(attacker)).isPlaneswalker();
|
||||||
|
boolean wantToTradeDownToSavePW = chanceToTradeDownToSaveWalker > 0;
|
||||||
|
|
||||||
|
if (((evalBlk <= evalAtk + 1) || (wantToSavePlaneswalker && wantToTradeDownToSavePW)) // "1" accounts for tapped.
|
||||||
|
&& powerParityOrHigher
|
||||||
|
&& (creatureParityOrAllowedDiff || wantToTradeWithCreatInHand)
|
||||||
|
&& (MyRandom.percentTrue(chance) || wantToSavePlaneswalker)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,17 +41,27 @@ import java.util.Set;
|
|||||||
public class AiCardMemory {
|
public class AiCardMemory {
|
||||||
|
|
||||||
private final Set<Card> memMandatoryAttackers;
|
private final Set<Card> memMandatoryAttackers;
|
||||||
|
private final Set<Card> memTrickAttackers;
|
||||||
private final Set<Card> memHeldManaSources;
|
private final Set<Card> memHeldManaSources;
|
||||||
|
private final Set<Card> memHeldManaSourcesForCombat;
|
||||||
|
private final Set<Card> memHeldManaSourcesForEnemyCombat;
|
||||||
private final Set<Card> memAttachedThisTurn;
|
private final Set<Card> memAttachedThisTurn;
|
||||||
private final Set<Card> memAnimatedThisTurn;
|
private final Set<Card> memAnimatedThisTurn;
|
||||||
private final Set<Card> memBouncedThisTurn;
|
private final Set<Card> memBouncedThisTurn;
|
||||||
|
private final Set<Card> memActivatedThisTurn;
|
||||||
|
private final Set<Card> memChosenFogEffect;
|
||||||
|
|
||||||
public AiCardMemory() {
|
public AiCardMemory() {
|
||||||
this.memMandatoryAttackers = new HashSet<>();
|
this.memMandatoryAttackers = new HashSet<>();
|
||||||
this.memHeldManaSources = new HashSet<>();
|
this.memHeldManaSources = new HashSet<>();
|
||||||
|
this.memHeldManaSourcesForCombat = new HashSet<>();
|
||||||
|
this.memHeldManaSourcesForEnemyCombat = new HashSet<>();
|
||||||
this.memAttachedThisTurn = new HashSet<>();
|
this.memAttachedThisTurn = new HashSet<>();
|
||||||
this.memAnimatedThisTurn = new HashSet<>();
|
this.memAnimatedThisTurn = new HashSet<>();
|
||||||
this.memBouncedThisTurn = new HashSet<>();
|
this.memBouncedThisTurn = new HashSet<>();
|
||||||
|
this.memActivatedThisTurn = new HashSet<>();
|
||||||
|
this.memTrickAttackers = new HashSet<>();
|
||||||
|
this.memChosenFogEffect = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,10 +71,15 @@ public class AiCardMemory {
|
|||||||
*/
|
*/
|
||||||
public enum MemorySet {
|
public enum MemorySet {
|
||||||
MANDATORY_ATTACKERS,
|
MANDATORY_ATTACKERS,
|
||||||
HELD_MANA_SOURCES,
|
TRICK_ATTACKERS,
|
||||||
|
HELD_MANA_SOURCES_FOR_MAIN2,
|
||||||
|
HELD_MANA_SOURCES_FOR_DECLBLK,
|
||||||
|
HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK,
|
||||||
ATTACHED_THIS_TURN,
|
ATTACHED_THIS_TURN,
|
||||||
ANIMATED_THIS_TURN,
|
ANIMATED_THIS_TURN,
|
||||||
BOUNCED_THIS_TURN,
|
BOUNCED_THIS_TURN,
|
||||||
|
ACTIVATED_THIS_TURN,
|
||||||
|
CHOSEN_FOG_EFFECT,
|
||||||
//REVEALED_CARDS // stub, not linked to AI code yet
|
//REVEALED_CARDS // stub, not linked to AI code yet
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,14 +87,24 @@ public class AiCardMemory {
|
|||||||
switch (set) {
|
switch (set) {
|
||||||
case MANDATORY_ATTACKERS:
|
case MANDATORY_ATTACKERS:
|
||||||
return memMandatoryAttackers;
|
return memMandatoryAttackers;
|
||||||
case HELD_MANA_SOURCES:
|
case TRICK_ATTACKERS:
|
||||||
|
return memTrickAttackers;
|
||||||
|
case HELD_MANA_SOURCES_FOR_MAIN2:
|
||||||
return memHeldManaSources;
|
return memHeldManaSources;
|
||||||
|
case HELD_MANA_SOURCES_FOR_DECLBLK:
|
||||||
|
return memHeldManaSourcesForCombat;
|
||||||
|
case HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK:
|
||||||
|
return memHeldManaSourcesForEnemyCombat;
|
||||||
case ATTACHED_THIS_TURN:
|
case ATTACHED_THIS_TURN:
|
||||||
return memAttachedThisTurn;
|
return memAttachedThisTurn;
|
||||||
case ANIMATED_THIS_TURN:
|
case ANIMATED_THIS_TURN:
|
||||||
return memAnimatedThisTurn;
|
return memAnimatedThisTurn;
|
||||||
case BOUNCED_THIS_TURN:
|
case BOUNCED_THIS_TURN:
|
||||||
return memBouncedThisTurn;
|
return memBouncedThisTurn;
|
||||||
|
case ACTIVATED_THIS_TURN:
|
||||||
|
return memActivatedThisTurn;
|
||||||
|
case CHOSEN_FOG_EFFECT:
|
||||||
|
return memChosenFogEffect;
|
||||||
//case REVEALED_CARDS:
|
//case REVEALED_CARDS:
|
||||||
// return memRevealedCards;
|
// return memRevealedCards;
|
||||||
default:
|
default:
|
||||||
@@ -249,10 +274,15 @@ public class AiCardMemory {
|
|||||||
*/
|
*/
|
||||||
public void clearAllRemembered() {
|
public void clearAllRemembered() {
|
||||||
clearMemorySet(MemorySet.MANDATORY_ATTACKERS);
|
clearMemorySet(MemorySet.MANDATORY_ATTACKERS);
|
||||||
clearMemorySet(MemorySet.HELD_MANA_SOURCES);
|
clearMemorySet(MemorySet.TRICK_ATTACKERS);
|
||||||
|
clearMemorySet(MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
|
||||||
|
clearMemorySet(MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK);
|
||||||
|
clearMemorySet(MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
|
||||||
clearMemorySet(MemorySet.ATTACHED_THIS_TURN);
|
clearMemorySet(MemorySet.ATTACHED_THIS_TURN);
|
||||||
clearMemorySet(MemorySet.ANIMATED_THIS_TURN);
|
clearMemorySet(MemorySet.ANIMATED_THIS_TURN);
|
||||||
clearMemorySet(MemorySet.BOUNCED_THIS_TURN);
|
clearMemorySet(MemorySet.BOUNCED_THIS_TURN);
|
||||||
|
clearMemorySet(MemorySet.ACTIVATED_THIS_TURN);
|
||||||
|
clearMemorySet(MemorySet.CHOSEN_FOG_EFFECT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static functions to simplify access to AI card memory of a given AI player.
|
// Static functions to simplify access to AI card memory of a given AI player.
|
||||||
@@ -265,6 +295,9 @@ public class AiCardMemory {
|
|||||||
public static boolean isRememberedCard(Player ai, Card c, MemorySet set) {
|
public static boolean isRememberedCard(Player ai, Card c, MemorySet set) {
|
||||||
return ((PlayerControllerAi)ai.getController()).getAi().getCardMemory().isRememberedCard(c, set);
|
return ((PlayerControllerAi)ai.getController()).getAi().getCardMemory().isRememberedCard(c, set);
|
||||||
}
|
}
|
||||||
|
public static boolean isRememberedCardByName(Player ai, String name, MemorySet set) {
|
||||||
|
return ((PlayerControllerAi)ai.getController()).getAi().getCardMemory().isRememberedCardByName(name, set);
|
||||||
|
}
|
||||||
public static void clearMemorySet(Player ai, MemorySet set) {
|
public static void clearMemorySet(Player ai, MemorySet set) {
|
||||||
((PlayerControllerAi)ai.getController()).getAi().getCardMemory().clearMemorySet(set);
|
((PlayerControllerAi)ai.getController()).getAi().getCardMemory().clearMemorySet(set);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,6 @@
|
|||||||
*/
|
*/
|
||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import java.security.InvalidParameterException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import com.esotericsoftware.minlog.Log;
|
import com.esotericsoftware.minlog.Log;
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
@@ -32,52 +24,32 @@ import com.google.common.base.Predicates;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
import forge.ai.ability.ChangeZoneAi;
|
import forge.ai.ability.ChangeZoneAi;
|
||||||
|
import forge.ai.ability.ExploreAi;
|
||||||
import forge.ai.simulation.SpellAbilityPicker;
|
import forge.ai.simulation.SpellAbilityPicker;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
import forge.deck.CardPool;
|
import forge.deck.CardPool;
|
||||||
import forge.deck.Deck;
|
import forge.deck.Deck;
|
||||||
import forge.deck.DeckSection;
|
import forge.deck.DeckSection;
|
||||||
import forge.game.CardTraitBase;
|
import forge.game.*;
|
||||||
import forge.game.CardTraitPredicates;
|
|
||||||
import forge.game.Direction;
|
|
||||||
import forge.game.Game;
|
|
||||||
import forge.game.GameEntity;
|
|
||||||
import forge.game.GlobalRuleChange;
|
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.ability.SpellApiBased;
|
import forge.game.ability.SpellApiBased;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.*;
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardCollectionView;
|
|
||||||
import forge.game.card.CardFactoryUtil;
|
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.card.CardPredicates;
|
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
import forge.game.card.CounterType;
|
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.*;
|
||||||
import forge.game.cost.CostDiscard;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.cost.CostPart;
|
|
||||||
import forge.game.cost.CostPutCounter;
|
|
||||||
import forge.game.cost.CostRemoveCounter;
|
|
||||||
import forge.game.mana.ManaCostBeingPaid;
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.replacement.ReplaceMoved;
|
import forge.game.replacement.ReplaceMoved;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.spellability.AbilityManaPart;
|
import forge.game.spellability.*;
|
||||||
import forge.game.spellability.AbilitySub;
|
|
||||||
import forge.game.spellability.OptionalCost;
|
|
||||||
import forge.game.spellability.Spell;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
|
||||||
import forge.game.spellability.SpellAbilityCondition;
|
|
||||||
import forge.game.spellability.SpellAbilityPredicates;
|
|
||||||
import forge.game.spellability.SpellPermanent;
|
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.trigger.WrappedAbility;
|
import forge.game.trigger.WrappedAbility;
|
||||||
@@ -88,6 +60,10 @@ import forge.util.Expressions;
|
|||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
|
|
||||||
|
import java.security.InvalidParameterException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* AiController class.
|
* AiController class.
|
||||||
@@ -151,10 +127,12 @@ public class AiController {
|
|||||||
|
|
||||||
private List<SpellAbility> getPossibleETBCounters() {
|
private List<SpellAbility> getPossibleETBCounters() {
|
||||||
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand));
|
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand));
|
||||||
|
CardCollectionView ccvPlayerLibrary = player.getCardsIn(ZoneType.Library);
|
||||||
|
|
||||||
all.addAll(player.getCardsIn(ZoneType.Exile));
|
all.addAll(player.getCardsIn(ZoneType.Exile));
|
||||||
all.addAll(player.getCardsIn(ZoneType.Graveyard));
|
all.addAll(player.getCardsIn(ZoneType.Graveyard));
|
||||||
if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
|
if (!ccvPlayerLibrary.isEmpty()) {
|
||||||
all.add(player.getCardsIn(ZoneType.Library).get(0));
|
all.add(ccvPlayerLibrary.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final Player opp : player.getOpponents()) {
|
for (final Player opp : player.getOpponents()) {
|
||||||
@@ -177,30 +155,34 @@ public class AiController {
|
|||||||
|
|
||||||
// look for cards on the battlefield that should prevent the AI from using that spellability
|
// look for cards on the battlefield that should prevent the AI from using that spellability
|
||||||
private boolean checkCurseEffects(final SpellAbility sa) {
|
private boolean checkCurseEffects(final SpellAbility sa) {
|
||||||
for (final Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
CardCollectionView ccvGameBattlefield = game.getCardsIn(ZoneType.Battlefield);
|
||||||
|
for (final Card c : ccvGameBattlefield) {
|
||||||
if (c.hasSVar("AICurseEffect")) {
|
if (c.hasSVar("AICurseEffect")) {
|
||||||
final String curse = c.getSVar("AICurseEffect");
|
final String curse = c.getSVar("AICurseEffect");
|
||||||
final Card host = sa.getHostCard();
|
|
||||||
if ("NonActive".equals(curse) && !player.equals(game.getPhaseHandler().getPlayerTurn())) {
|
if ("NonActive".equals(curse) && !player.equals(game.getPhaseHandler().getPlayerTurn())) {
|
||||||
return true;
|
return true;
|
||||||
} else if ("DestroyCreature".equals(curse) && sa.isSpell() && host.isCreature()
|
} else {
|
||||||
&& !sa.getHostCard().hasKeyword("Indestructible")) {
|
final Card host = sa.getHostCard();
|
||||||
return true;
|
if ("DestroyCreature".equals(curse) && sa.isSpell() && host.isCreature()
|
||||||
} else if ("CounterEnchantment".equals(curse) && sa.isSpell() && host.isEnchantment()
|
&& !host.hasKeyword(Keyword.INDESTRUCTIBLE)) {
|
||||||
&& !sa.getHostCard().hasKeyword("CARDNAME can't be countered.")) {
|
return true;
|
||||||
return true;
|
} else if ("CounterEnchantment".equals(curse) && sa.isSpell() && host.isEnchantment()
|
||||||
} else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && !host.hasKeyword("CARDNAME can't be countered.")
|
&& CardFactoryUtil.isCounterable(host)) {
|
||||||
&& host.getCMC() == c.getCounters(CounterType.CHARGE)) {
|
return true;
|
||||||
return true;
|
} else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)
|
||||||
} else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && !host.hasKeyword("CARDNAME can't be countered.")) {
|
&& host.getCMC() == c.getCounters(CounterType.CHARGE)) {
|
||||||
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
return true;
|
||||||
if (!card.isToken() && card.getName().equals(host.getName())) {
|
} else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)) {
|
||||||
return true;
|
String hostName = host.getName();
|
||||||
|
for (Card card : ccvGameBattlefield) {
|
||||||
|
if (!card.isToken() && card.getName().equals(hostName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
for (Card card : game.getCardsIn(ZoneType.Graveyard)) {
|
||||||
for (Card card : game.getCardsIn(ZoneType.Graveyard)) {
|
if (card.getName().equals(hostName)) {
|
||||||
if (card.getName().equals(host.getName())) {
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,13 +192,14 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean checkETBEffects(final Card card, final SpellAbility sa, final ApiType api) {
|
public boolean checkETBEffects(final Card card, final SpellAbility sa, final ApiType api) {
|
||||||
boolean rightapi = false;
|
|
||||||
|
|
||||||
if (card.isCreature()
|
if (card.isCreature()
|
||||||
&& game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCreatureETBTriggers)) {
|
&& game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCreatureETBTriggers)) {
|
||||||
return api == null;
|
return api == null;
|
||||||
}
|
}
|
||||||
|
boolean rightapi = false;
|
||||||
|
String battlefield = ZoneType.Battlefield.toString();
|
||||||
|
Player activatingPlayer = sa.getActivatingPlayer();
|
||||||
|
|
||||||
// Trigger play improvements
|
// Trigger play improvements
|
||||||
for (final Trigger tr : card.getTriggers()) {
|
for (final Trigger tr : card.getTriggers()) {
|
||||||
// These triggers all care for ETB effects
|
// These triggers all care for ETB effects
|
||||||
@@ -226,21 +209,22 @@ public class AiController {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) {
|
if (!params.get("Destination").equals(battlefield)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.containsKey("ValidCard")) {
|
if (params.containsKey("ValidCard")) {
|
||||||
if (!params.get("ValidCard").contains("Self")) {
|
String validCard = params.get("ValidCard");
|
||||||
|
if (!validCard.contains("Self")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.get("ValidCard").contains("notkicked")) {
|
if (validCard.contains("notkicked")) {
|
||||||
if (sa.isKicked()) {
|
if (sa.isKicked()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else if (params.get("ValidCard").contains("kicked")) {
|
} else if (validCard.contains("kicked")) {
|
||||||
if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker
|
if (validCard.contains("kicked ")) { // want a specific kicker
|
||||||
String s = params.get("ValidCard").split("kicked ")[1];
|
String s = validCard.split("kicked ")[1];
|
||||||
if ("1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue;
|
if ("1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue;
|
||||||
if ("2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue;
|
if ("2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue;
|
||||||
} else if (!sa.isKicked()) {
|
} else if (!sa.isKicked()) {
|
||||||
@@ -283,7 +267,7 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sa != null) {
|
if (sa != null) {
|
||||||
exSA.setActivatingPlayer(sa.getActivatingPlayer());
|
exSA.setActivatingPlayer(activatingPlayer);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
exSA.setActivatingPlayer(player);
|
exSA.setActivatingPlayer(player);
|
||||||
@@ -291,13 +275,11 @@ public class AiController {
|
|||||||
exSA.setTrigger(true);
|
exSA.setTrigger(true);
|
||||||
|
|
||||||
// for trigger test, need to ignore the conditions
|
// for trigger test, need to ignore the conditions
|
||||||
if (exSA.getConditions() != null) {
|
SpellAbilityCondition cons = exSA.getConditions();
|
||||||
SpellAbilityCondition cons = exSA.getConditions();
|
if (cons != null) {
|
||||||
if (cons.getIsPresent() != null) {
|
String pres = cons.getIsPresent();
|
||||||
String pres = cons.getIsPresent();
|
if (pres != null && pres.matches("Card\\.(Strictly)?Self")) {
|
||||||
if ("Card.Self".equals(pres) || "Card.StrictlySelf".equals(pres)) {
|
|
||||||
cons.setIsPresent(null);
|
cons.setIsPresent(null);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,21 +303,22 @@ public class AiController {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) {
|
if (!params.get("Destination").equals(battlefield)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.containsKey("ValidCard")) {
|
if (params.containsKey("ValidCard")) {
|
||||||
if (!params.get("ValidCard").contains("Self")) {
|
String validCard = params.get("ValidCard");
|
||||||
|
if (!validCard.contains("Self")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.get("ValidCard").contains("notkicked")) {
|
if (validCard.contains("notkicked")) {
|
||||||
if (sa.isKicked()) {
|
if (sa.isKicked()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else if (params.get("ValidCard").contains("kicked")) {
|
} else if (validCard.contains("kicked")) {
|
||||||
if (params.get("ValidCard").contains("kicked ")) { // want a specific kicker
|
if (validCard.contains("kicked ")) { // want a specific kicker
|
||||||
String s = params.get("ValidCard").split("kicked ")[1];
|
String s = validCard.split("kicked ")[1];
|
||||||
if ("1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue;
|
if ("1".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker1)) continue;
|
||||||
if ("2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue;
|
if ("2".equals(s) && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) continue;
|
||||||
} else if (!sa.isKicked()) { // otherwise just any must be present
|
} else if (!sa.isKicked()) { // otherwise just any must be present
|
||||||
@@ -351,7 +334,7 @@ public class AiController {
|
|||||||
|
|
||||||
if (exSA != null) {
|
if (exSA != null) {
|
||||||
if (sa != null) {
|
if (sa != null) {
|
||||||
exSA.setActivatingPlayer(sa.getActivatingPlayer());
|
exSA.setActivatingPlayer(activatingPlayer);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
exSA.setActivatingPlayer(player);
|
exSA.setActivatingPlayer(player);
|
||||||
@@ -399,8 +382,9 @@ public class AiController {
|
|||||||
if (landsInPlay.size() + landList.size() > max) {
|
if (landsInPlay.size() + landList.size() > max) {
|
||||||
for (Card c : allCards) {
|
for (Card c : allCards) {
|
||||||
for (SpellAbility sa : c.getSpellAbilities()) {
|
for (SpellAbility sa : c.getSpellAbilities()) {
|
||||||
if (sa.getPayCosts() != null) {
|
Cost payCosts = sa.getPayCosts();
|
||||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
if (payCosts != null) {
|
||||||
|
for (CostPart part : payCosts.getCostParts()) {
|
||||||
if (part instanceof CostDiscard) {
|
if (part instanceof CostDiscard) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -414,10 +398,11 @@ public class AiController {
|
|||||||
landList = CardLists.filter(landList, new Predicate<Card>() {
|
landList = CardLists.filter(landList, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
|
CardCollectionView battlefield = player.getCardsIn(ZoneType.Battlefield);
|
||||||
canPlaySpellBasic(c, null);
|
canPlaySpellBasic(c, null);
|
||||||
if (c.getType().isLegendary() && !c.getName().equals("Flagstones of Trokair")) {
|
String name = c.getName();
|
||||||
final CardCollectionView list = player.getCardsIn(ZoneType.Battlefield);
|
if (c.getType().isLegendary() && !name.equals("Flagstones of Trokair")) {
|
||||||
if (Iterables.any(list, CardPredicates.nameEquals(c.getName()))) {
|
if (Iterables.any(battlefield, CardPredicates.nameEquals(name))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -426,7 +411,7 @@ public class AiController {
|
|||||||
final FCollectionView<SpellAbility> spellAbilities = c.getSpellAbilities();
|
final FCollectionView<SpellAbility> spellAbilities = c.getSpellAbilities();
|
||||||
|
|
||||||
final CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
|
final CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
|
||||||
CardCollection lands = new CardCollection(player.getCardsIn(ZoneType.Battlefield));
|
CardCollection lands = new CardCollection(battlefield);
|
||||||
lands.addAll(hand);
|
lands.addAll(hand);
|
||||||
lands = CardLists.filter(lands, CardPredicates.Presets.LANDS);
|
lands = CardLists.filter(lands, CardPredicates.Presets.LANDS);
|
||||||
int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc);
|
int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc);
|
||||||
@@ -437,7 +422,8 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
return player.canPlayLand(c);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return landList;
|
return landList;
|
||||||
@@ -589,14 +575,17 @@ public class AiController {
|
|||||||
Collections.sort(all, saComparator); // put best spells first
|
Collections.sort(all, saComparator); // put best spells first
|
||||||
|
|
||||||
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
|
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
|
||||||
if (sa.getApi() == ApiType.Counter || sa.getApi() == exceptSA) {
|
ApiType saApi = sa.getApi();
|
||||||
|
|
||||||
|
if (saApi == ApiType.Counter || saApi == exceptSA) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sa.setActivatingPlayer(player);
|
sa.setActivatingPlayer(player);
|
||||||
// TODO: this currently only works as a limited prediction of permanent spells.
|
// TODO: this currently only works as a limited prediction of permanent spells.
|
||||||
// Ideally this should cast canPlaySa to determine that the AI is truly able/willing to cast a spell,
|
// Ideally this should cast canPlaySa to determine that the AI is truly able/willing to cast a spell,
|
||||||
// but that is currently difficult to implement due to various side effects leading to stack overflow.
|
// but that is currently difficult to implement due to various side effects leading to stack overflow.
|
||||||
if (!ComputerUtil.castPermanentInMain1(player, sa) && sa.getHostCard() != null && !sa.getHostCard().isLand() && ComputerUtilCost.canPayCost(sa, player)) {
|
Card host = sa.getHostCard();
|
||||||
|
if (!ComputerUtil.castPermanentInMain1(player, sa) && host != null && !host.isLand() && ComputerUtilCost.canPayCost(sa, player)) {
|
||||||
if (sa instanceof SpellPermanent) {
|
if (sa instanceof SpellPermanent) {
|
||||||
return sa;
|
return sa;
|
||||||
}
|
}
|
||||||
@@ -605,11 +594,33 @@ public class AiController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reserveManaSourcesForMain2(SpellAbility sa) {
|
public void reserveManaSources(SpellAbility sa) {
|
||||||
|
reserveManaSources(sa, PhaseType.MAIN2, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reserveManaSources(SpellAbility sa, PhaseType phaseType, boolean enemy) {
|
||||||
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0);
|
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0);
|
||||||
CardCollection manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player);
|
CardCollection manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player);
|
||||||
|
|
||||||
|
AiCardMemory.MemorySet memSet;
|
||||||
|
|
||||||
|
switch (phaseType) {
|
||||||
|
case MAIN2:
|
||||||
|
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2;
|
||||||
|
break;
|
||||||
|
case COMBAT_DECLARE_BLOCKERS:
|
||||||
|
memSet = enemy ? AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK
|
||||||
|
: AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.out.println("Warning: unsupported mana reservation phase specified for reserveManaSources: "
|
||||||
|
+ phaseType.name() + ", reserving until Main 2 instead. Consider adding support for the phase if needed.");
|
||||||
|
memSet = AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
for (Card c : manaSources) {
|
for (Card c : manaSources) {
|
||||||
AiCardMemory.rememberCard(player, c, AiCardMemory.MemorySet.HELD_MANA_SOURCES);
|
AiCardMemory.rememberCard(player, c, memSet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,6 +677,11 @@ public class AiController {
|
|||||||
if (sa instanceof SpellPermanent) {
|
if (sa instanceof SpellPermanent) {
|
||||||
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
|
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
|
||||||
}
|
}
|
||||||
|
if (sa.usesTargeting() && !sa.isTargetNumberValid()) {
|
||||||
|
if (!sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||||
|
return AiPlayDecision.TargetingFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (sa instanceof Spell) {
|
if (sa instanceof Spell) {
|
||||||
if (ComputerUtil.getDamageForPlaying(player, sa) >= player.getLife()
|
if (ComputerUtil.getDamageForPlaying(player, sa) >= player.getLife()
|
||||||
&& !player.cantLoseForZeroOrLessLife() && player.canLoseLife()) {
|
&& !player.cantLoseForZeroOrLessLife() && player.canLoseLife()) {
|
||||||
@@ -676,44 +692,36 @@ public class AiController {
|
|||||||
return AiPlayDecision.WillPlay;
|
return AiPlayDecision.WillPlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AiPlayDecision canPlaySpellBasic(final Card card, final SpellAbility sa) {
|
public boolean isNonDisabledCardInPlay(final String cardName) {
|
||||||
boolean isRightSplit = sa != null && sa.isRightSplit();
|
for (Card card : player.getCardsIn(ZoneType.Battlefield)) {
|
||||||
String needsToPlayName = isRightSplit ? "SplitNeedsToPlay" : "NeedsToPlay";
|
if (card.getName().equals(cardName)) {
|
||||||
String needsToPlayVarName = isRightSplit ? "SplitNeedsToPlayVar": "NeedsToPlayVar";
|
// TODO - Better logic to detemine if a permanent is disabled by local effects
|
||||||
|
// currently assuming any permanent enchanted by another player
|
||||||
if (card.hasSVar(needsToPlayName)) {
|
// is disabled and a second copy is necessary
|
||||||
final String needsToPlay = card.getSVar(needsToPlayName);
|
// will need actual logic that determines if the enchantment is able
|
||||||
CardCollectionView list = game.getCardsIn(ZoneType.Battlefield);
|
// to disable the permanent or it's still functional and a duplicate is unneeded.
|
||||||
|
boolean disabledByEnemy = false;
|
||||||
list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card, null);
|
for (Card card2 : card.getEnchantedBy(false)) {
|
||||||
if (list.isEmpty()) {
|
if (card2.getOwner() != player) {
|
||||||
return AiPlayDecision.MissingNeededCards;
|
disabledByEnemy = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!disabledByEnemy) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (card.getSVar(needsToPlayVarName).length() > 0) {
|
return false;
|
||||||
final String needsToPlay = card.getSVar(needsToPlayVarName);
|
|
||||||
int x = 0;
|
|
||||||
int y = 0;
|
|
||||||
String sVar = needsToPlay.split(" ")[0];
|
|
||||||
String comparator = needsToPlay.split(" ")[1];
|
|
||||||
String compareTo = comparator.substring(2);
|
|
||||||
try {
|
|
||||||
x = Integer.parseInt(sVar);
|
|
||||||
} catch (final NumberFormatException e) {
|
|
||||||
x = CardFactoryUtil.xCount(card, card.getSVar(sVar));
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
y = Integer.parseInt(compareTo);
|
|
||||||
} catch (final NumberFormatException e) {
|
|
||||||
y = CardFactoryUtil.xCount(card, card.getSVar(compareTo));
|
|
||||||
}
|
|
||||||
if (!Expressions.compare(x, comparator, y)) {
|
|
||||||
return AiPlayDecision.NeedsToPlayCriteriaNotMet;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return AiPlayDecision.WillPlay;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AiPlayDecision canPlaySpellBasic(final Card card, final SpellAbility sa) {
|
||||||
|
if ("True".equals(card.getSVar("NonStackingEffect")) && isNonDisabledCardInPlay(card.getName())) {
|
||||||
|
return AiPlayDecision.NeedsToPlayCriteriaNotMet;
|
||||||
|
}
|
||||||
|
// add any other necessary logic to play a basic spell here
|
||||||
|
return ComputerUtilCard.checkNeedsToPlayReqs(card, sa);
|
||||||
|
}
|
||||||
|
|
||||||
// not sure "playing biggest spell" matters?
|
// not sure "playing biggest spell" matters?
|
||||||
private final static Comparator<SpellAbility> saComparator = new Comparator<SpellAbility>() {
|
private final static Comparator<SpellAbility> saComparator = new Comparator<SpellAbility>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -724,12 +732,31 @@ public class AiController {
|
|||||||
int b1 = b.getPayCosts() == null ? 0 : b.getPayCosts().getTotalMana().getCMC();
|
int b1 = b.getPayCosts() == null ? 0 : b.getPayCosts().getTotalMana().getCMC();
|
||||||
|
|
||||||
// deprioritize planar die roll marked with AIRollPlanarDieParams:LowPriority$ True
|
// deprioritize planar die roll marked with AIRollPlanarDieParams:LowPriority$ True
|
||||||
if (ApiType.RollPlanarDice == a.getApi() && a.getHostCard().hasSVar("AIRollPlanarDieParams") && a.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) {
|
if (ApiType.RollPlanarDice == a.getApi() && a.getHostCard() != null && a.getHostCard().hasSVar("AIRollPlanarDieParams") && a.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) {
|
||||||
return 1;
|
return 1;
|
||||||
} else if (ApiType.RollPlanarDice == b.getApi() && b.getHostCard().hasSVar("AIRollPlanarDieParams") && b.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) {
|
} else if (ApiType.RollPlanarDice == b.getApi() && b.getHostCard() != null && b.getHostCard().hasSVar("AIRollPlanarDieParams") && b.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deprioritize pump spells with pure energy cost (can be activated last,
|
||||||
|
// since energy is generally scarce, plus can benefit e.g. Electrostatic Pummeler)
|
||||||
|
int a2 = 0, b2 = 0;
|
||||||
|
if (a.getApi() == ApiType.Pump && a.getPayCosts() != null && a.getPayCosts().getCostEnergy() != null) {
|
||||||
|
if (a.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||||
|
a2 = a.getPayCosts().getCostEnergy().convertAmount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (b.getApi() == ApiType.Pump && b.getPayCosts() != null && b.getPayCosts().getCostEnergy() != null) {
|
||||||
|
if (b.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||||
|
b2 = b.getPayCosts().getCostEnergy().convertAmount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a2 == 0 && b2 > 0) {
|
||||||
|
return -1;
|
||||||
|
} else if (b2 == 0 && a2 > 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// cast 0 mana cost spells first (might be a Mox)
|
// cast 0 mana cost spells first (might be a Mox)
|
||||||
if (a1 == 0 && b1 > 0 && ApiType.Mana != a.getApi()) {
|
if (a1 == 0 && b1 > 0 && ApiType.Mana != a.getApi()) {
|
||||||
return -1;
|
return -1;
|
||||||
@@ -737,9 +764,9 @@ public class AiController {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.getHostCard().hasSVar("FreeSpellAI")) {
|
if (a.getHostCard() != null && a.getHostCard().hasSVar("FreeSpellAI")) {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (b.getHostCard().hasSVar("FreeSpellAI")) {
|
} else if (b.getHostCard() != null && b.getHostCard().hasSVar("FreeSpellAI")) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -752,35 +779,42 @@ public class AiController {
|
|||||||
private int getSpellAbilityPriority(SpellAbility sa) {
|
private int getSpellAbilityPriority(SpellAbility sa) {
|
||||||
int p = 0;
|
int p = 0;
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
final Player ai = source.getController();
|
final Player ai = source == null ? sa.getActivatingPlayer() : source.getController();
|
||||||
|
if (ai == null) {
|
||||||
|
System.err.println("Error: couldn't figure out the activating player and host card for SA: " + sa);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
final boolean noCreatures = ai.getCreaturesInPlay().isEmpty();
|
final boolean noCreatures = ai.getCreaturesInPlay().isEmpty();
|
||||||
// puts creatures in front of spells
|
|
||||||
if (source.isCreature()) {
|
if (source != null) {
|
||||||
p += 1;
|
// puts creatures in front of spells
|
||||||
}
|
if (source.isCreature()) {
|
||||||
// don't play equipments before having any creatures
|
p += 1;
|
||||||
if (source.isEquipment() && noCreatures) {
|
}
|
||||||
p -= 9;
|
// don't play equipments before having any creatures
|
||||||
}
|
if (source.isEquipment() && noCreatures) {
|
||||||
// use Surge and Prowl costs when able to
|
p -= 9;
|
||||||
if (sa.isSurged() ||
|
}
|
||||||
(sa.getRestrictions().getProwlTypes() != null && !sa.getRestrictions().getProwlTypes().isEmpty())) {
|
// 1. increase chance of using Surge effects
|
||||||
p += 9;
|
// 2. non-surged versions are usually inefficient
|
||||||
}
|
if (source.getOracleText().contains("surge cost") && !sa.isSurged()) {
|
||||||
// 1. increase chance of using Surge effects
|
p -= 9;
|
||||||
// 2. non-surged versions are usually inefficient
|
}
|
||||||
if (sa.getHostCard().getOracleText().contains("surge cost") && !sa.isSurged()) {
|
// move snap-casted spells to front
|
||||||
p -= 9;
|
if (source.isInZone(ZoneType.Graveyard)) {
|
||||||
}
|
if (sa.getMayPlay() != null && source.mayPlay(sa.getMayPlay()) != null) {
|
||||||
// move snap-casted spells to front
|
p += 50;
|
||||||
if (source.isInZone(ZoneType.Graveyard)) {
|
}
|
||||||
if(sa.getMayPlay() != null && source.mayPlay(sa.getMayPlay()) != null) {
|
}
|
||||||
p += 50;
|
// if the profile specifies it, deprioritize Storm spells in an attempt to build up storm count
|
||||||
|
if (source.hasKeyword(Keyword.STORM) && ai.getController() instanceof PlayerControllerAi) {
|
||||||
|
p -= (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.PRIORITY_REDUCTION_FOR_STORM_SPELLS));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// artifacts and enchantments with effects that do not stack
|
|
||||||
if ("True".equals(source.getSVar("NonStackingEffect")) && ai.isCardInPlay(source.getName())) {
|
// use Surge and Prowl costs when able to
|
||||||
p -= 9;
|
if (sa.isSurged() || sa.isProwl()) {
|
||||||
|
p += 9;
|
||||||
}
|
}
|
||||||
// sort planeswalker abilities with most costly first
|
// sort planeswalker abilities with most costly first
|
||||||
if (sa.getRestrictions().isPwAbility()) {
|
if (sa.getRestrictions().isPwAbility()) {
|
||||||
@@ -801,11 +835,6 @@ public class AiController {
|
|||||||
p -= 9;
|
p -= 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the profile specifies it, deprioritize Storm spells in an attempt to build up storm count
|
|
||||||
if (source.hasKeyword("Storm") && ai.getController() instanceof PlayerControllerAi) {
|
|
||||||
p -= (((PlayerControllerAi)ai.getController()).getAi().getIntProperty(AiProps.PRIORITY_REDUCTION_FOR_STORM_SPELLS));
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to cast mana ritual spells before casting spells to maximize potential mana
|
// try to cast mana ritual spells before casting spells to maximize potential mana
|
||||||
if ("ManaRitual".equals(sa.getParam("AILogic"))) {
|
if ("ManaRitual".equals(sa.getParam("AILogic"))) {
|
||||||
p += 9;
|
p += 9;
|
||||||
@@ -840,7 +869,36 @@ public class AiController {
|
|||||||
sourceCard = sa.getHostCard();
|
sourceCard = sa.getHostCard();
|
||||||
if ("Always".equals(sa.getParam("AILogic")) && !validCards.isEmpty()) {
|
if ("Always".equals(sa.getParam("AILogic")) && !validCards.isEmpty()) {
|
||||||
min = 1;
|
min = 1;
|
||||||
|
} else if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) {
|
||||||
|
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("AnyNumber")) {
|
||||||
|
if ("DiscardUncastableAndExcess".equals(sa.getParam("AILogic"))) {
|
||||||
|
CardCollection discards = new CardCollection();
|
||||||
|
final CardCollectionView inHand = player.getCardsIn(ZoneType.Hand);
|
||||||
|
final int numLandsOTB = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size();
|
||||||
|
int numOppInHand = 0;
|
||||||
|
for (Player p : player.getGame().getPlayers()) {
|
||||||
|
if (p.getCardsIn(ZoneType.Hand).size() > numOppInHand) {
|
||||||
|
numOppInHand = p.getCardsIn(ZoneType.Hand).size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Card c : inHand) {
|
||||||
|
if (c.hasSVar("DoNotDiscardIfAble") || c.hasSVar("IsReanimatorCard")) { continue; }
|
||||||
|
if (c.isCreature() && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getSpellPermanent(), player)) {
|
||||||
|
discards.add(c);
|
||||||
|
}
|
||||||
|
if ((c.isLand() && numLandsOTB >= 5) || (c.getFirstSpellAbility() != null && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getFirstSpellAbility(), player))) {
|
||||||
|
if (discards.size() + 1 <= numOppInHand) {
|
||||||
|
discards.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return discards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// look for good discards
|
// look for good discards
|
||||||
@@ -858,6 +916,9 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
if (prefCard == null) {
|
if (prefCard == null) {
|
||||||
prefCard = ComputerUtil.getCardPreference(player, sourceCard, "DiscardCost", validCards);
|
prefCard = ComputerUtil.getCardPreference(player, sourceCard, "DiscardCost", validCards);
|
||||||
|
if (prefCard != null && prefCard.hasSVar("DoNotDiscardIfAble")) {
|
||||||
|
prefCard = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (prefCard != null) {
|
if (prefCard != null) {
|
||||||
discardList.add(prefCard);
|
discardList.add(prefCard);
|
||||||
@@ -894,13 +955,52 @@ public class AiController {
|
|||||||
if (numLandsInHand > 0) {
|
if (numLandsInHand > 0) {
|
||||||
numLandsAvailable++;
|
numLandsAvailable++;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Discard unplayable card
|
//Discard unplayable card
|
||||||
if (validCards.get(0).getCMC() > numLandsAvailable) {
|
boolean discardedUnplayable = false;
|
||||||
discardList.add(validCards.get(0));
|
for (int j = 0; j < validCards.size(); j++) {
|
||||||
validCards.remove(validCards.get(0));
|
if (validCards.get(j).getCMC() > numLandsAvailable && !validCards.get(j).hasSVar("DoNotDiscardIfAble")) {
|
||||||
|
discardList.add(validCards.get(j));
|
||||||
|
validCards.remove(validCards.get(j));
|
||||||
|
discardedUnplayable = true;
|
||||||
|
break;
|
||||||
|
} else if (validCards.get(j).getCMC() <= numLandsAvailable) {
|
||||||
|
// cut short to avoid looping over cards which are guaranteed not to fit the criteria
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else { //Discard worst card
|
|
||||||
|
if (!discardedUnplayable) {
|
||||||
|
// discard worst card
|
||||||
Card worst = ComputerUtilCard.getWorstAI(validCards);
|
Card worst = ComputerUtilCard.getWorstAI(validCards);
|
||||||
|
if (worst == null) {
|
||||||
|
// there were only instants and sorceries, and maybe cards that are not good to discard, so look
|
||||||
|
// for more discard options
|
||||||
|
worst = ComputerUtilCard.getCheapestSpellAI(validCards);
|
||||||
|
}
|
||||||
|
if (worst == null && !validCards.isEmpty()) {
|
||||||
|
// still nothing chosen, so choose the first thing that works, trying not to make DoNotDiscardIfAble
|
||||||
|
// discards
|
||||||
|
for (Card c : validCards) {
|
||||||
|
if (!c.hasSVar("DoNotDiscardIfAble")) {
|
||||||
|
worst = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Only DoNotDiscardIfAble cards? If we have a duplicate for something, discard it
|
||||||
|
if (worst == null) {
|
||||||
|
for (Card c : validCards) {
|
||||||
|
if (CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.nameEquals(c.getName())).size() > 1) {
|
||||||
|
worst = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (worst == null) {
|
||||||
|
// Otherwise just grab a random card and discard it
|
||||||
|
worst = Aggregates.random(validCards);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
discardList.add(worst);
|
discardList.add(worst);
|
||||||
validCards.remove(worst);
|
validCards.remove(worst);
|
||||||
}
|
}
|
||||||
@@ -1059,17 +1159,46 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CardCollection landsWannaPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player);
|
CardCollection landsWannaPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player);
|
||||||
|
CardCollection playBeforeLand = CardLists.filter(
|
||||||
|
player.getCardsIn(ZoneType.Hand), CardPredicates.hasSVar("PlayBeforeLandDrop")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!playBeforeLand.isEmpty()) {
|
||||||
|
SpellAbility wantToPlayBeforeLand = chooseSpellAbilityToPlayFromList(
|
||||||
|
ComputerUtilAbility.getSpellAbilities(playBeforeLand, player), false
|
||||||
|
);
|
||||||
|
if (wantToPlayBeforeLand != null) {
|
||||||
|
return singleSpellAbilityList(wantToPlayBeforeLand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (landsWannaPlay != null) {
|
if (landsWannaPlay != null) {
|
||||||
landsWannaPlay = filterLandsToPlay(landsWannaPlay);
|
landsWannaPlay = filterLandsToPlay(landsWannaPlay);
|
||||||
Log.debug("Computer " + game.getPhaseHandler().getPhase().nameForUi);
|
Log.debug("Computer " + game.getPhaseHandler().getPhase().nameForUi);
|
||||||
if (landsWannaPlay != null && !landsWannaPlay.isEmpty() && player.canPlayLand(null)) {
|
if (landsWannaPlay != null && !landsWannaPlay.isEmpty()) {
|
||||||
|
// TODO search for other land it might want to play?
|
||||||
Card land = chooseBestLandToPlay(landsWannaPlay);
|
Card land = chooseBestLandToPlay(landsWannaPlay);
|
||||||
if (ComputerUtil.getDamageFromETB(player, land) < player.getLife() || !player.canLoseLife()
|
if (ComputerUtil.getDamageFromETB(player, land) < player.getLife() || !player.canLoseLife()
|
||||||
|| player.cantLoseForZeroOrLessLife() ) {
|
|| player.cantLoseForZeroOrLessLife() ) {
|
||||||
game.PLAY_LAND_SURROGATE.setHostCard(land);
|
if (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land)) {
|
||||||
final List<SpellAbility> abilities = Lists.newArrayList();
|
final List<SpellAbility> abilities = Lists.newArrayList();
|
||||||
abilities.add(game.PLAY_LAND_SURROGATE);
|
|
||||||
return abilities;
|
LandAbility la = new LandAbility(land, player, null);
|
||||||
|
if (la.canPlay()) {
|
||||||
|
abilities.add(la);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add mayPlay option
|
||||||
|
for (CardPlayOption o : land.mayPlay(player)) {
|
||||||
|
la = new LandAbility(land, player, o.getAbility());
|
||||||
|
if (la.canPlay()) {
|
||||||
|
abilities.add(la);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!abilities.isEmpty()) {
|
||||||
|
return abilities;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1077,6 +1206,116 @@ public class AiController {
|
|||||||
return singleSpellAbilityList(getSpellAbilityToPlay());
|
return singleSpellAbilityList(getSpellAbilityToPlay());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isSafeToHoldLandDropForMain2(Card landToPlay) {
|
||||||
|
boolean hasMomir = !CardLists.filter(player.getCardsIn(ZoneType.Command),
|
||||||
|
CardPredicates.nameEquals("Momir Vig, Simic Visionary Avatar")).isEmpty();
|
||||||
|
if (hasMomir) {
|
||||||
|
// Don't do this in Momir variants since it messes with the AI decision making for the avatar.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!MyRandom.percentTrue(getIntProperty(AiProps.HOLD_LAND_DROP_FOR_MAIN2_IF_UNUSED))) {
|
||||||
|
// check against the chance specified in the profile
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (game.getPhaseHandler().getTurn() <= 2) {
|
||||||
|
// too obvious when doing it on the very first turn of the game
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CardCollection inHand = CardLists.filter(player.getCardsIn(ZoneType.Hand),
|
||||||
|
Predicates.not(CardPredicates.Presets.LANDS));
|
||||||
|
CardCollectionView otb = player.getCardsIn(ZoneType.Battlefield);
|
||||||
|
|
||||||
|
if (getBooleanProperty(AiProps.HOLD_LAND_DROP_ONLY_IF_HAVE_OTHER_PERMS)) {
|
||||||
|
if (CardLists.filter(otb, Predicates.not(CardPredicates.Presets.LANDS)).isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: improve the detection of taplands
|
||||||
|
boolean isTapLand = false;
|
||||||
|
for (ReplacementEffect repl : landToPlay.getReplacementEffects()) {
|
||||||
|
if (repl.getParamOrDefault("Description", "").equals("CARDNAME enters the battlefield tapped.")) {
|
||||||
|
isTapLand = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalCMCInHand = Aggregates.sum(inHand, CardPredicates.Accessors.fnGetCmc);
|
||||||
|
int minCMCInHand = Aggregates.min(inHand, CardPredicates.Accessors.fnGetCmc);
|
||||||
|
int predictedMana = ComputerUtilMana.getAvailableManaEstimate(player, true);
|
||||||
|
|
||||||
|
boolean canCastWithLandDrop = (predictedMana + 1 >= minCMCInHand) && !isTapLand;
|
||||||
|
boolean cantCastAnythingNow = predictedMana < minCMCInHand;
|
||||||
|
|
||||||
|
boolean hasRelevantAbsOTB = !CardLists.filter(otb, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(Card card) {
|
||||||
|
boolean isTapLand = false;
|
||||||
|
for (ReplacementEffect repl : card.getReplacementEffects()) {
|
||||||
|
// TODO: improve the detection of taplands
|
||||||
|
if (repl.getParamOrDefault("Description", "").equals("CARDNAME enters the battlefield tapped.")) {
|
||||||
|
isTapLand = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SpellAbility sa : card.getSpellAbilities()) {
|
||||||
|
if (sa.getPayCosts() != null && sa.isAbility()
|
||||||
|
&& sa.getPayCosts().getCostMana() != null
|
||||||
|
&& sa.getPayCosts().getCostMana().getMana().getCMC() > 0
|
||||||
|
&& (!sa.getPayCosts().hasTapCost() || !isTapLand)
|
||||||
|
&& (!sa.hasParam("ActivationZone") || sa.getParam("ActivationZone").contains("Battlefield"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}).isEmpty();
|
||||||
|
|
||||||
|
boolean hasLandBasedEffect = !CardLists.filter(otb, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(Card card) {
|
||||||
|
for (Trigger t : card.getTriggers()) {
|
||||||
|
Map<String, String> params = t.getMapParams();
|
||||||
|
if ("ChangesZone".equals(params.get("Mode"))
|
||||||
|
&& params.containsKey("ValidCard")
|
||||||
|
&& !params.get("ValidCard").contains("nonLand")
|
||||||
|
&& ((params.get("ValidCard").contains("Land")) || (params.get("ValidCard").contains("Permanent")))
|
||||||
|
&& "Battlefield".equals(params.get("Destination"))) {
|
||||||
|
// Landfall and other similar triggers
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String sv : card.getSVars().keySet()) {
|
||||||
|
String varValue = card.getSVar(sv);
|
||||||
|
if (varValue.startsWith("Count$Valid") || sv.equals("BuffedBy")) {
|
||||||
|
if (varValue.contains("Land") || varValue.contains("Plains") || varValue.contains("Forest")
|
||||||
|
|| varValue.contains("Mountain") || varValue.contains("Island") || varValue.contains("Swamp")
|
||||||
|
|| varValue.contains("Wastes")) {
|
||||||
|
// In presence of various cards that get buffs like "equal to the number of lands you control",
|
||||||
|
// safer for our AI model to just play the land earlier rather than make a blunder
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}).isEmpty();
|
||||||
|
|
||||||
|
// TODO: add prediction for effects that will untap a tapland as it enters the battlefield
|
||||||
|
if (!canCastWithLandDrop && cantCastAnythingNow && !hasLandBasedEffect && (!hasRelevantAbsOTB || isTapLand)) {
|
||||||
|
// Hopefully there's not much to do with the extra mana immediately, can wait for Main 2
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ((predictedMana <= totalCMCInHand && canCastWithLandDrop) || (hasRelevantAbsOTB && !isTapLand) || hasLandBasedEffect) {
|
||||||
|
// Might need an extra land to cast something, or for some kind of an ETB ability with a cost or an
|
||||||
|
// alternative cost (if we cast it in Main 1), or to use an activated ability on the battlefield
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private final SpellAbility getSpellAbilityToPlay() {
|
private final SpellAbility getSpellAbilityToPlay() {
|
||||||
// if top of stack is owned by me
|
// if top of stack is owned by me
|
||||||
if (!game.getStack().isEmpty() && game.getStack().peekAbility().getActivatingPlayer().equals(player)) {
|
if (!game.getStack().isEmpty() && game.getStack().peekAbility().getActivatingPlayer().equals(player)) {
|
||||||
@@ -1109,7 +1348,7 @@ public class AiController {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getHostCard().hasKeyword("Storm")
|
if (sa.getHostCard().hasKeyword(Keyword.STORM)
|
||||||
&& sa.getApi() != ApiType.Counter // AI would suck at trying to deliberately proc a Storm counterspell
|
&& sa.getApi() != ApiType.Counter // AI would suck at trying to deliberately proc a Storm counterspell
|
||||||
&& CardLists.filter(player.getCardsIn(ZoneType.Hand), Predicates.not(Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.hasKeyword("Storm")))).size() > 0) {
|
&& CardLists.filter(player.getCardsIn(ZoneType.Hand), Predicates.not(Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.hasKeyword("Storm")))).size() > 0) {
|
||||||
if (game.getView().getStormCount() < this.getIntProperty(AiProps.MIN_COUNT_FOR_STORM_SPELLS)) {
|
if (game.getView().getStormCount() < this.getIntProperty(AiProps.MIN_COUNT_FOR_STORM_SPELLS)) {
|
||||||
@@ -1245,23 +1484,25 @@ public class AiController {
|
|||||||
|
|
||||||
boolean hasLeyline1 = false;
|
boolean hasLeyline1 = false;
|
||||||
SpellAbility saGemstones = null;
|
SpellAbility saGemstones = null;
|
||||||
|
|
||||||
for(int i = 0; i < result.size(); i++) {
|
List<SpellAbility> toRemove = Lists.newArrayList();
|
||||||
SpellAbility sa = result.get(i);
|
for(SpellAbility sa : result) {
|
||||||
|
|
||||||
String srcName = sa.getHostCard().getName();
|
String srcName = sa.getHostCard().getName();
|
||||||
if ("Gemstone Caverns".equals(srcName)) {
|
if ("Gemstone Caverns".equals(srcName)) {
|
||||||
if (saGemstones == null)
|
if (saGemstones == null)
|
||||||
saGemstones = sa;
|
saGemstones = sa;
|
||||||
else
|
else
|
||||||
result.remove(i--);
|
toRemove.add(sa);
|
||||||
} else if ("Leyline of Singularity".equals(srcName)) {
|
} else if ("Leyline of Singularity".equals(srcName)) {
|
||||||
if (!hasLeyline1)
|
if (!hasLeyline1)
|
||||||
hasLeyline1 = true;
|
hasLeyline1 = true;
|
||||||
else
|
else
|
||||||
result.remove(i--);
|
toRemove.add(sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for(SpellAbility sa : toRemove) {
|
||||||
|
result.remove(sa);
|
||||||
|
}
|
||||||
|
|
||||||
// Play them last
|
// Play them last
|
||||||
if (saGemstones != null) {
|
if (saGemstones != null) {
|
||||||
@@ -1306,7 +1547,7 @@ public class AiController {
|
|||||||
+ MyRandom.getRandom().nextInt(3);
|
+ MyRandom.getRandom().nextInt(3);
|
||||||
return Math.max(remaining, min) / 2;
|
return Math.max(remaining, min) / 2;
|
||||||
} else if ("LowestLoseLife".equals(logic)) {
|
} else if ("LowestLoseLife".equals(logic)) {
|
||||||
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getOpponent().getLife())) + 1;
|
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, ComputerUtil.getOpponentFor(player).getLife())) + 1;
|
||||||
} else if ("HighestGetCounter".equals(logic)) {
|
} else if ("HighestGetCounter".equals(logic)) {
|
||||||
return MyRandom.getRandom().nextInt(3);
|
return MyRandom.getRandom().nextInt(3);
|
||||||
} else if (source.hasSVar("EnergyToPay")) {
|
} else if (source.hasSVar("EnergyToPay")) {
|
||||||
@@ -1337,11 +1578,11 @@ public class AiController {
|
|||||||
// and exaclty one counter of the specifice type gets high priority to keep the card
|
// and exaclty one counter of the specifice type gets high priority to keep the card
|
||||||
if (allies.contains(crd.getController())) {
|
if (allies.contains(crd.getController())) {
|
||||||
// except if its a Chronozoa, because it WANTS to be removed to make more
|
// except if its a Chronozoa, because it WANTS to be removed to make more
|
||||||
if (crd.hasKeyword("Vanishing") && !"Chronozoa".equals(crd.getName())) {
|
if (crd.hasKeyword(Keyword.VANISHING) && !"Chronozoa".equals(crd.getName())) {
|
||||||
if (crd.getCounters(CounterType.TIME) == 1) {
|
if (crd.getCounters(CounterType.TIME) == 1) {
|
||||||
return CounterType.TIME;
|
return CounterType.TIME;
|
||||||
}
|
}
|
||||||
} else if (crd.hasKeyword("Fading")) {
|
} else if (crd.hasKeyword(Keyword.FADING)) {
|
||||||
if (crd.getCounters(CounterType.FADE) == 1) {
|
if (crd.getCounters(CounterType.FADE) == 1) {
|
||||||
return CounterType.FADE;
|
return CounterType.FADE;
|
||||||
}
|
}
|
||||||
@@ -1428,6 +1669,24 @@ public class AiController {
|
|||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special case for Bow to My Command which simulates a complex tap cost via ChooseCard
|
||||||
|
// TODO: consider enhancing support for tapXType<Any/...> in UnlessCost to get rid of this hack
|
||||||
|
if ("BowToMyCommand".equals(sa.getParam("AILogic"))) {
|
||||||
|
if (!sa.getHostCard().getZone().is(ZoneType.Command)) {
|
||||||
|
// Make sure that other opponents do not tap for an already abandoned scheme
|
||||||
|
result.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int totPower = 0;
|
||||||
|
for (Card p : result) {
|
||||||
|
totPower += p.getNetPower();
|
||||||
|
}
|
||||||
|
if (totPower >= 8) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1553,7 +1812,11 @@ public class AiController {
|
|||||||
if (useSimulation) {
|
if (useSimulation) {
|
||||||
return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
||||||
}
|
}
|
||||||
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
if (sa.getApi() == ApiType.Explore) {
|
||||||
|
return ExploreAi.shouldPutInGraveyard(fetchList, decider);
|
||||||
|
} else {
|
||||||
|
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SpellAbility> orderPlaySa(List<SpellAbility> activePlayerSAs) {
|
public List<SpellAbility> orderPlaySa(List<SpellAbility> activePlayerSAs) {
|
||||||
@@ -1575,6 +1838,10 @@ public class AiController {
|
|||||||
|
|
||||||
List<SpellAbility> evolve = filterList(putCounter, SpellAbilityPredicates.hasParam("Evolve"));
|
List<SpellAbility> evolve = filterList(putCounter, SpellAbilityPredicates.hasParam("Evolve"));
|
||||||
|
|
||||||
|
List<SpellAbility> token = filterListByApi(activePlayerSAs, ApiType.Token);
|
||||||
|
List<SpellAbility> pump = filterListByApi(activePlayerSAs, ApiType.Pump);
|
||||||
|
List<SpellAbility> pumpAll = filterListByApi(activePlayerSAs, ApiType.PumpAll);
|
||||||
|
|
||||||
// do mandatory discard early if hand is empty or has DiscardMe card
|
// do mandatory discard early if hand is empty or has DiscardMe card
|
||||||
boolean discardEarly = false;
|
boolean discardEarly = false;
|
||||||
CardCollectionView playerHand = player.getCardsIn(ZoneType.Hand);
|
CardCollectionView playerHand = player.getCardsIn(ZoneType.Hand);
|
||||||
@@ -1583,6 +1850,11 @@ public class AiController {
|
|||||||
result.addAll(mandatoryDiscard);
|
result.addAll(mandatoryDiscard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// token should be added first so they might get the pump bonus
|
||||||
|
result.addAll(token);
|
||||||
|
result.addAll(pump);
|
||||||
|
result.addAll(pumpAll);
|
||||||
|
|
||||||
// do Evolve Trigger before other PutCounter SpellAbilities
|
// do Evolve Trigger before other PutCounter SpellAbilities
|
||||||
// do putCounter before Draw/Discard because it can cause a Draw Trigger
|
// do putCounter before Draw/Discard because it can cause a Draw Trigger
|
||||||
result.addAll(evolve);
|
result.addAll(evolve);
|
||||||
@@ -1598,6 +1870,9 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.addAll(activePlayerSAs);
|
result.addAll(activePlayerSAs);
|
||||||
|
|
||||||
|
//need to reverse because of magic stack
|
||||||
|
Collections.reverse(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,21 +1,21 @@
|
|||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
public enum AiPlayDecision {
|
public enum AiPlayDecision {
|
||||||
WillPlay,
|
WillPlay,
|
||||||
CantPlaySa,
|
CantPlaySa,
|
||||||
CantPlayAi,
|
CantPlayAi,
|
||||||
CantAfford,
|
CantAfford,
|
||||||
CantAffordX,
|
CantAffordX,
|
||||||
WaitForMain2,
|
WaitForMain2,
|
||||||
AnotherTime,
|
AnotherTime,
|
||||||
MissingNeededCards,
|
MissingNeededCards,
|
||||||
NeedsToPlayCriteriaNotMet,
|
NeedsToPlayCriteriaNotMet,
|
||||||
TargetingFailed,
|
TargetingFailed,
|
||||||
CostNotAcceptable,
|
CostNotAcceptable,
|
||||||
WouldDestroyLegend,
|
WouldDestroyLegend,
|
||||||
WouldDestroyOtherPlaneswalker,
|
WouldDestroyOtherPlaneswalker,
|
||||||
WouldBecomeZeroToughnessCreature,
|
WouldBecomeZeroToughnessCreature,
|
||||||
WouldDestroyWorldEnchantment,
|
WouldDestroyWorldEnchantment,
|
||||||
BadEtbEffects,
|
BadEtbEffects,
|
||||||
CurseEffects;
|
CurseEffects;
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,7 @@ import forge.LobbyPlayer;
|
|||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.FileUtil;
|
import forge.util.FileUtil;
|
||||||
|
|
||||||
|
import forge.util.TextUtil;
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -52,7 +53,7 @@ public class AiProfileUtil {
|
|||||||
* @return the full relative path and file name for the given profile.
|
* @return the full relative path and file name for the given profile.
|
||||||
*/
|
*/
|
||||||
private static String buildFileName(final String profileName) {
|
private static String buildFileName(final String profileName) {
|
||||||
return String.format("%s/%s%s", AI_PROFILE_DIR, profileName, AI_PROFILE_EXT);
|
return TextUtil.concatNoSpace(AI_PROFILE_DIR, "/", profileName, AI_PROFILE_EXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,67 +1,129 @@
|
|||||||
/*
|
/*
|
||||||
* Forge: Play Magic: the Gathering.
|
* Forge: Play Magic: the Gathering.
|
||||||
* Copyright (C) 2013 Forge Team
|
* Copyright (C) 2013 Forge Team
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AI personality profile settings identifiers, and their default values.
|
* AI personality profile settings identifiers, and their default values.
|
||||||
* When this class is instantiated, these enum values are used
|
* When this class is instantiated, these enum values are used
|
||||||
* in a map that is populated with the current AI profile settings
|
* in a map that is populated with the current AI profile settings
|
||||||
* from the text file.
|
* from the text file.
|
||||||
*/
|
*/
|
||||||
public enum AiProps { /** */
|
public enum AiProps { /** */
|
||||||
DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN ("1"), /** */
|
DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN ("1"), /** */
|
||||||
DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE ("3"), /** */
|
DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE ("3"), /** */
|
||||||
DEFAULT_PLANAR_DIE_ROLL_CHANCE ("50"), /** */
|
DEFAULT_PLANAR_DIE_ROLL_CHANCE ("50"), /** */
|
||||||
MULLIGAN_THRESHOLD ("5"), /** */
|
MULLIGAN_THRESHOLD ("5"), /** */
|
||||||
PLANAR_DIE_ROLL_HESITATION_CHANCE ("10"),
|
PLANAR_DIE_ROLL_HESITATION_CHANCE ("10"),
|
||||||
CHEAT_WITH_MANA_ON_SHUFFLE ("false"),
|
HOLD_LAND_DROP_FOR_MAIN2_IF_UNUSED ("0"), /** */
|
||||||
MOVE_EQUIPMENT_TO_BETTER_CREATURES ("from_useless_only"),
|
HOLD_LAND_DROP_ONLY_IF_HAVE_OTHER_PERMS ("true"), /** */
|
||||||
MOVE_EQUIPMENT_CREATURE_EVAL_THRESHOLD ("60"),
|
CHEAT_WITH_MANA_ON_SHUFFLE ("false"),
|
||||||
PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS ("true"),
|
MOVE_EQUIPMENT_TO_BETTER_CREATURES ("from_useless_only"),
|
||||||
PREDICT_SPELLS_FOR_MAIN2 ("true"), /** */
|
MOVE_EQUIPMENT_CREATURE_EVAL_THRESHOLD ("60"),
|
||||||
RESERVE_MANA_FOR_MAIN2_CHANCE ("0"), /** */
|
PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS ("true"),
|
||||||
PLAY_AGGRO ("false"), /** */
|
PREDICT_SPELLS_FOR_MAIN2 ("true"), /** */
|
||||||
MIN_SPELL_CMC_TO_COUNTER ("0"),
|
RESERVE_MANA_FOR_MAIN2_CHANCE ("0"), /** */
|
||||||
ALWAYS_COUNTER_OTHER_COUNTERSPELLS ("true"), /** */
|
PLAY_AGGRO ("false"),
|
||||||
ALWAYS_COUNTER_DAMAGE_SPELLS ("true"), /** */
|
CHANCE_TO_ATTACK_INTO_TRADE ("40"), /** */
|
||||||
ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS ("true"), /** */
|
RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE ("true"), /** */
|
||||||
ALWAYS_COUNTER_REMOVAL_SPELLS ("true"), /** */
|
ATTACK_INTO_TRADE_WHEN_TAPPED_OUT ("false"), /** */
|
||||||
ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS (""), /** */
|
CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA ("0"), /** */
|
||||||
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("false"), /** */
|
TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK ("true"), /** */
|
||||||
PRIORITY_REDUCTION_FOR_STORM_SPELLS ("0"), /** */
|
TRY_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK ("false"), /** */
|
||||||
USE_BERSERK_AGGRESSIVELY ("false"), /** */
|
CHANCE_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK ("30"), /** */
|
||||||
MIN_COUNT_FOR_STORM_SPELLS ("0"), /** */
|
ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK ("true"), /** */
|
||||||
STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE ("1"), /** */
|
RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS ("false"), /** */
|
||||||
STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK ("3"), /** */
|
MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE ("1"), /** */
|
||||||
STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK ("6"), /** */
|
ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT ("true"), /** */
|
||||||
STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING ("3"), /** */
|
MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE_WITH_REPL ("1"), /** */
|
||||||
STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP ("false"); /** */
|
MIN_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK ("30"), /** */
|
||||||
|
MAX_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK ("70"), /** */
|
||||||
private final String strDefaultVal;
|
CHANCE_DECREASE_TO_TRADE_VS_EMBALM ("30"), /** */
|
||||||
|
CHANCE_TO_TRADE_TO_SAVE_PLANESWALKER ("70"), /** */
|
||||||
/** @param s0   {@link java.lang.String} */
|
CHANCE_TO_TRADE_DOWN_TO_SAVE_PLANESWALKER ("0"), /** */
|
||||||
AiProps(final String s0) {
|
THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER ("135"), /** */
|
||||||
this.strDefaultVal = s0;
|
THRESHOLD_NONTOKEN_CHUMP_TO_SAVE_PLANESWALKER ("110"), /** */
|
||||||
}
|
CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL ("true"), /** */
|
||||||
|
MIN_SPELL_CMC_TO_COUNTER ("0"), /** */
|
||||||
/** @return {@link java.lang.String} */
|
CHANCE_TO_COUNTER_CMC_1 ("50"), /** */
|
||||||
public String getDefault() {
|
CHANCE_TO_COUNTER_CMC_2 ("75"), /** */
|
||||||
return strDefaultVal;
|
CHANCE_TO_COUNTER_CMC_3 ("100"), /** */
|
||||||
}
|
ALWAYS_COUNTER_OTHER_COUNTERSPELLS ("true"), /** */
|
||||||
}
|
ALWAYS_COUNTER_DAMAGE_SPELLS ("true"), /** */
|
||||||
|
ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS ("true"), /** */
|
||||||
|
ALWAYS_COUNTER_REMOVAL_SPELLS ("true"), /** */
|
||||||
|
ALWAYS_COUNTER_PUMP_SPELLS ("true"), /** */
|
||||||
|
ALWAYS_COUNTER_AURAS ("true"), /** */
|
||||||
|
ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS (""), /** */
|
||||||
|
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("true"), /** */
|
||||||
|
ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE ("false"), /** */
|
||||||
|
DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD ("2"), /** */
|
||||||
|
DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR ("true"), /** */
|
||||||
|
DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR ("5"), /** */
|
||||||
|
PRIORITY_REDUCTION_FOR_STORM_SPELLS ("0"), /** */
|
||||||
|
USE_BERSERK_AGGRESSIVELY ("false"), /** */
|
||||||
|
MIN_COUNT_FOR_STORM_SPELLS ("0"), /** */
|
||||||
|
STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE ("1"), /** */
|
||||||
|
STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK ("3"), /** */
|
||||||
|
STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK ("6"), /** */
|
||||||
|
STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING ("3"), /** */
|
||||||
|
STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP ("false"),
|
||||||
|
TOKEN_GENERATION_ABILITY_CHANCE ("80"), /** */
|
||||||
|
TOKEN_GENERATION_ALWAYS_IF_FROM_PLANESWALKER ("true"), /** */
|
||||||
|
TOKEN_GENERATION_ALWAYS_IF_OPP_ATTACKS ("true"), /** */
|
||||||
|
SCRY_NUM_LANDS_TO_STILL_NEED_MORE ("4"), /** */
|
||||||
|
SCRY_NUM_LANDS_TO_NOT_NEED_MORE ("7"), /** */
|
||||||
|
SCRY_NUM_CREATURES_TO_NOT_NEED_SUBPAR_ONES ("4"), /** */
|
||||||
|
SCRY_EVALTHR_CREATCOUNT_TO_SCRY_AWAY_LOWCMC ("3"), /** */
|
||||||
|
SCRY_EVALTHR_TO_SCRY_AWAY_LOWCMC_CREATURE ("160"), /** */
|
||||||
|
SCRY_EVALTHR_CMC_THRESHOLD ("3"), /** */
|
||||||
|
SCRY_IMMEDIATELY_UNCASTABLE_TO_BOTTOM ("false"), /** */
|
||||||
|
SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF ("1"), /** */
|
||||||
|
SURVEIL_NUM_CARDS_IN_LIBRARY_TO_BAIL ("10"), /** */
|
||||||
|
COMBAT_ASSAULT_ATTACK_EVASION_PREDICTION ("true"), /** */
|
||||||
|
COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION ("true"), /** */
|
||||||
|
CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT ("true"), /** */
|
||||||
|
CONSERVATIVE_ENERGY_PAYMENT_ONLY_DEFENSIVELY ("true"), /** */
|
||||||
|
BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF ("200"), /** */
|
||||||
|
BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF ("200"), /** */
|
||||||
|
BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF ("3"), /** */
|
||||||
|
BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF ("3"), /** */
|
||||||
|
INTUITION_ALTERNATIVE_LOGIC ("false"), /** */
|
||||||
|
EXPLORE_MAX_CMC_DIFF_TO_PUT_IN_GRAVEYARD ("2"),
|
||||||
|
EXPLORE_NUM_LANDS_TO_STILL_NEED_MORE("2"), /** */
|
||||||
|
MOJHOSTO_NUM_LANDS_TO_ACTIVATE_JHOIRA("5"), /** */
|
||||||
|
MOJHOSTO_CHANCE_TO_PREFER_JHOIRA_OVER_MOMIR ("50"), /** */
|
||||||
|
MOJHOSTO_CHANCE_TO_USE_JHOIRA_COPY_INSTANT ("20"), /** */
|
||||||
|
// Experimental features, must be removed after extensive testing and, ideally, defaulting
|
||||||
|
// <-- There are no experimental options here -->
|
||||||
|
AI_IN_DANGER_THRESHOLD("4"),
|
||||||
|
AI_IN_DANGER_MAX_THRESHOLD("4");
|
||||||
|
|
||||||
|
|
||||||
|
private final String strDefaultVal;
|
||||||
|
|
||||||
|
/** @param s0   {@link java.lang.String} */
|
||||||
|
AiProps(final String s0) {
|
||||||
|
this.strDefaultVal = s0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return {@link java.lang.String} */
|
||||||
|
public String getDefault() {
|
||||||
|
return strDefaultVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,23 +17,9 @@
|
|||||||
*/
|
*/
|
||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.*;
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
import com.google.common.collect.Multimap;
|
|
||||||
|
|
||||||
import forge.ai.ability.ProtectAi;
|
import forge.ai.ability.ProtectAi;
|
||||||
import forge.ai.ability.TokenAi;
|
import forge.ai.ability.TokenAi;
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
@@ -46,32 +32,18 @@ import forge.game.ability.AbilityFactory;
|
|||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.ability.effects.CharmEffect;
|
import forge.game.ability.effects.CharmEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.*;
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardCollectionView;
|
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.card.CardPredicates;
|
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
import forge.game.card.CardUtil;
|
|
||||||
import forge.game.card.CounterType;
|
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.*;
|
||||||
import forge.game.cost.CostDiscard;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.cost.CostPart;
|
|
||||||
import forge.game.cost.CostPayment;
|
|
||||||
import forge.game.cost.CostPutCounter;
|
|
||||||
import forge.game.cost.CostSacrifice;
|
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.replacement.ReplacementLayer;
|
import forge.game.replacement.ReplacementLayer;
|
||||||
import forge.game.spellability.AbilityManaPart;
|
import forge.game.spellability.*;
|
||||||
import forge.game.spellability.AbilitySub;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
|
||||||
import forge.game.spellability.SpellAbilityStackInstance;
|
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
@@ -79,7 +51,11 @@ import forge.game.zone.Zone;
|
|||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
import forge.util.TextUtil;
|
||||||
import forge.util.collect.FCollection;
|
import forge.util.collect.FCollection;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,14 +75,28 @@ public class ComputerUtil {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
if (sa.isSpell() && !source.isCopiedSpell()) {
|
if (sa.isSpell() && !source.isCopiedSpell()) {
|
||||||
|
if (source.getType().hasStringType("Arcane")) {
|
||||||
|
sa = AbilityUtils.addSpliceEffects(sa);
|
||||||
|
if (sa.getSplicedCards() != null && !sa.getSplicedCards().isEmpty() && ai.getController().isAI()) {
|
||||||
|
// we need to reconsider and retarget the SA after additional SAs have been added onto it via splice,
|
||||||
|
// otherwise the AI will fail to add the card to stack and that'll knock it out of the game
|
||||||
|
sa.resetTargets();
|
||||||
|
if (((PlayerControllerAi) ai.getController()).getAi().canPlaySa(sa) != AiPlayDecision.WillPlay) {
|
||||||
|
// for whatever reason the AI doesn't want to play the thing with the spliced subs anymore,
|
||||||
|
// proceeding past this point may result in an illegal play
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
source.setCastSA(sa);
|
source.setCastSA(sa);
|
||||||
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
||||||
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
||||||
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
||||||
|
}
|
||||||
|
|
||||||
if (source.getType().hasStringType("Arcane")) {
|
if (sa.isCopied()) {
|
||||||
sa = AbilityUtils.addSpliceEffects(sa);
|
sa.resetPaidHash();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
|
||||||
@@ -192,7 +182,7 @@ public class ComputerUtil {
|
|||||||
if (unless != null && !unless.endsWith(">")) {
|
if (unless != null && !unless.endsWith(">")) {
|
||||||
final int amount = AbilityUtils.calculateAmount(source, unless, sa);
|
final int amount = AbilityUtils.calculateAmount(source, unless, sa);
|
||||||
|
|
||||||
final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size();
|
final int usableManaSources = ComputerUtilMana.getAvailableManaSources(ComputerUtil.getOpponentFor(ai), true).size();
|
||||||
|
|
||||||
// If the Unless isn't enough, this should be less likely to be used
|
// If the Unless isn't enough, this should be less likely to be used
|
||||||
if (amount > usableManaSources) {
|
if (amount > usableManaSources) {
|
||||||
@@ -272,6 +262,10 @@ public class ComputerUtil {
|
|||||||
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
||||||
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
||||||
newSA.setHostCard(game.getAction().moveToStack(source, sa));
|
newSA.setHostCard(game.getAction().moveToStack(source, sa));
|
||||||
|
|
||||||
|
if (newSA.getApi() == ApiType.Charm && !newSA.isWrapper()) {
|
||||||
|
CharmEffect.makeChoices(newSA);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final CostPayment pay = new CostPayment(newSA.getPayCosts(), newSA);
|
final CostPayment pay = new CostPayment(newSA.getPayCosts(), newSA);
|
||||||
@@ -310,43 +304,46 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
public static Card getCardPreference(final Player ai, final Card activate, final String pref, final CardCollection typeList) {
|
public static Card getCardPreference(final Player ai, final Card activate, final String pref, final CardCollection typeList) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
String prefDef = "";
|
||||||
if (activate != null) {
|
if (activate != null) {
|
||||||
|
prefDef = activate.getSVar("AIPreference");
|
||||||
final String[] prefGroups = activate.getSVar("AIPreference").split("\\|");
|
final String[] prefGroups = activate.getSVar("AIPreference").split("\\|");
|
||||||
for (String prefGroup : prefGroups) {
|
for (String prefGroup : prefGroups) {
|
||||||
final String[] prefValid = prefGroup.trim().split("\\$");
|
final String[] prefValid = prefGroup.trim().split("\\$");
|
||||||
if (prefValid[0].equals(pref)) {
|
if (prefValid[0].equals(pref) && !prefValid[1].startsWith("Special:")) {
|
||||||
final CardCollection prefList = CardLists.getValidCards(typeList, prefValid[1].split(","), activate.getController(), activate, null);
|
|
||||||
CardCollection overrideList = null;
|
CardCollection overrideList = null;
|
||||||
|
|
||||||
if (activate.hasSVar("AIPreferenceOverride")) {
|
if (activate.hasSVar("AIPreferenceOverride")) {
|
||||||
overrideList = CardLists.getValidCards(typeList, activate.getSVar("AIPreferenceOverride"), activate.getController(), activate, null);
|
overrideList = CardLists.getValidCards(typeList, activate.getSVar("AIPreferenceOverride"), activate.getController(), activate, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
int threshold = getAIPreferenceParameter(activate, "CreatureEvalThreshold");
|
for (String validItem : prefValid[1].split(",")) {
|
||||||
int minNeeded = getAIPreferenceParameter(activate, "MinCreaturesBelowThreshold");
|
final CardCollection prefList = CardLists.getValidCards(typeList, validItem, activate.getController(), activate, null);
|
||||||
|
int threshold = getAIPreferenceParameter(activate, "CreatureEvalThreshold");
|
||||||
|
int minNeeded = getAIPreferenceParameter(activate, "MinCreaturesBelowThreshold");
|
||||||
|
|
||||||
if (threshold != -1) {
|
if (threshold != -1) {
|
||||||
List<Card> toRemove = Lists.newArrayList();
|
List<Card> toRemove = Lists.newArrayList();
|
||||||
for (Card c : prefList) {
|
for (Card c : prefList) {
|
||||||
if (c.isCreature()) {
|
if (c.isCreature()) {
|
||||||
if (ComputerUtilCard.isUselessCreature(ai, c) || ComputerUtilCard.evaluateCreature(c) <= threshold) {
|
if (ComputerUtilCard.isUselessCreature(ai, c) || ComputerUtilCard.evaluateCreature(c) <= threshold) {
|
||||||
continue;
|
continue;
|
||||||
} else if (ComputerUtilCard.hasActiveUndyingOrPersist(c)) {
|
} else if (ComputerUtilCard.hasActiveUndyingOrPersist(c)) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
toRemove.add(c);
|
||||||
}
|
}
|
||||||
toRemove.add(c);
|
}
|
||||||
|
prefList.removeAll(toRemove);
|
||||||
|
}
|
||||||
|
if (minNeeded != -1) {
|
||||||
|
if (prefList.size() < minNeeded) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prefList.removeAll(toRemove);
|
|
||||||
}
|
|
||||||
if (minNeeded != -1) {
|
|
||||||
if (prefList.size() < minNeeded) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!prefList.isEmpty()) {
|
if (!prefList.isEmpty() || (overrideList != null && !overrideList.isEmpty())) {
|
||||||
return ComputerUtilCard.getWorstAI(overrideList == null ? prefList : overrideList);
|
return ComputerUtilCard.getWorstAI(overrideList == null ? prefList : overrideList);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -411,6 +408,11 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Survival of the Fittest logic
|
||||||
|
if (prefDef.contains("DiscardCost$Special:SurvivalOfTheFittest")) {
|
||||||
|
return SpecialCardAi.SurvivalOfTheFittest.considerDiscardTarget(ai);
|
||||||
|
}
|
||||||
|
|
||||||
// Discard lands
|
// Discard lands
|
||||||
final CardCollection landsInHand = CardLists.getType(typeList, "Land");
|
final CardCollection landsInHand = CardLists.getType(typeList, "Land");
|
||||||
if (!landsInHand.isEmpty()) {
|
if (!landsInHand.isEmpty()) {
|
||||||
@@ -707,6 +709,8 @@ public class ComputerUtil {
|
|||||||
return sacrificed; // sacrifice none
|
return sacrificed; // sacrifice none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
boolean exceptSelf = "ExceptSelf".equals(source.getParam("AILogic"));
|
||||||
|
boolean removedSelf = false;
|
||||||
|
|
||||||
if (isOptional && source.hasParam("Devour") || source.hasParam("Exploit") || considerSacLogic) {
|
if (isOptional && source.hasParam("Devour") || source.hasParam("Exploit") || considerSacLogic) {
|
||||||
if (source.hasParam("Exploit")) {
|
if (source.hasParam("Exploit")) {
|
||||||
@@ -764,11 +768,22 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
final int max = Math.min(remaining.size(), amount);
|
final int max = Math.min(remaining.size(), amount);
|
||||||
|
|
||||||
|
if (exceptSelf) {
|
||||||
|
removedSelf = remaining.remove(source.getHostCard());
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < max; i++) {
|
for (int i = 0; i < max; i++) {
|
||||||
Card c = chooseCardToSacrifice(remaining, ai, destroy);
|
Card c = chooseCardToSacrifice(remaining, ai, destroy);
|
||||||
remaining.remove(c);
|
remaining.remove(c);
|
||||||
sacrificed.add(c);
|
if (c != null) {
|
||||||
|
sacrificed.add(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sacrificed.isEmpty() && removedSelf) {
|
||||||
|
sacrificed.add(source.getHostCard());
|
||||||
|
}
|
||||||
|
|
||||||
return sacrificed;
|
return sacrificed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -781,7 +796,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (destroy) {
|
if (destroy) {
|
||||||
final CardCollection indestructibles = CardLists.getKeyword(remaining, "Indestructible");
|
final CardCollection indestructibles = CardLists.getKeyword(remaining, Keyword.INDESTRUCTIBLE);
|
||||||
if (!indestructibles.isEmpty()) {
|
if (!indestructibles.isEmpty()) {
|
||||||
return indestructibles.get(0);
|
return indestructibles.get(0);
|
||||||
}
|
}
|
||||||
@@ -818,7 +833,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean canRegenerate(Player ai, final Card card) {
|
public static boolean canRegenerate(Player ai, final Card card) {
|
||||||
if (card.hasKeyword("CARDNAME can't be regenerated.")) {
|
if (!card.canBeShielded()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -848,11 +863,11 @@ public class ComputerUtil {
|
|||||||
continue; // Won't play ability
|
continue; // Won't play ability
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(controller, abCost, c)) {
|
if (!ComputerUtilCost.checkSacrificeCost(controller, abCost, c, sa)) {
|
||||||
continue; // Won't play ability
|
continue; // Won't play ability
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkCreatureSacrificeCost(controller, abCost, c)) {
|
if (!ComputerUtilCost.checkCreatureSacrificeCost(controller, abCost, c, sa)) {
|
||||||
continue; // Won't play ability
|
continue; // Won't play ability
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -868,7 +883,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (final Exception ex) {
|
} catch (final Exception ex) {
|
||||||
throw new RuntimeException(String.format("There is an error in the card code for %s:%s", c.getName(), ex.getMessage()), ex);
|
throw new RuntimeException(TextUtil.concatNoSpace("There is an error in the card code for ", c.getName(), ":", ex.getMessage()), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -907,7 +922,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (final Exception ex) {
|
} catch (final Exception ex) {
|
||||||
throw new RuntimeException(String.format("There is an error in the card code for %s:%s", c.getName(), ex.getMessage()), ex);
|
throw new RuntimeException(TextUtil.concatNoSpace("There is an error in the card code for ", c.getName(), ":", ex.getMessage()), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -917,32 +932,38 @@ public class ComputerUtil {
|
|||||||
public static boolean castPermanentInMain1(final Player ai, final SpellAbility sa) {
|
public static boolean castPermanentInMain1(final Player ai, final SpellAbility sa) {
|
||||||
final Card card = sa.getHostCard();
|
final Card card = sa.getHostCard();
|
||||||
|
|
||||||
if ("True".equals(card.getSVar("NonStackingEffect")) && card.getController().isCardInPlay(card.getName())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (card.hasSVar("PlayMain1")) {
|
if (card.hasSVar("PlayMain1")) {
|
||||||
if (card.getSVar("PlayMain1").equals("ALWAYS") || sa.getPayCosts().hasNoManaCost()) {
|
if (card.getSVar("PlayMain1").equals("ALWAYS") || sa.getPayCosts().hasNoManaCost()) {
|
||||||
return true;
|
return true;
|
||||||
} else if (card.getSVar("PlayMain1").equals("OPPONENTCREATURES")) {
|
} else if (card.getSVar("PlayMain1").equals("OPPONENTCREATURES")) {
|
||||||
//Only play these main1 when the opponent has creatures (stealing and giving them haste)
|
//Only play these main1 when the opponent has creatures (stealing and giving them haste)
|
||||||
if (!card.getController().getOpponent().getCreaturesInPlay().isEmpty()) {
|
if (!ai.getOpponents().getCreaturesInPlay().isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (!card.getController().getCreaturesInPlay().isEmpty()) {
|
} else if (!card.getController().getCreaturesInPlay().isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// try not to cast Raid creatures in main 1 if an attack is likely
|
||||||
|
if ("Count$AttackersDeclared".equals(card.getSVar("RaidTest")) && !card.hasKeyword(Keyword.HASTE)) {
|
||||||
|
for (Card potentialAtkr: ai.getCreaturesInPlay()) {
|
||||||
|
if (ComputerUtilCard.doesCreatureAttackAI(ai, potentialAtkr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (card.getManaCost().isZero()) {
|
if (card.getManaCost().isZero()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (card.isCreature() && !card.hasKeyword("Defender") && (card.hasKeyword("Haste") || ComputerUtil.hasACardGivingHaste(ai) || sa.isDash())) {
|
if (card.isCreature() && !card.hasKeyword(Keyword.DEFENDER)
|
||||||
|
&& (card.hasKeyword(Keyword.HASTE) || ComputerUtil.hasACardGivingHaste(ai, true) || sa.isDash())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (card.hasKeyword("Exalted")) {
|
if (card.hasKeyword(Keyword.EXALTED)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -973,27 +994,27 @@ public class ComputerUtil {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (card.isEquipment() && buffedcard.isCreature() && CombatUtil.canAttack(buffedcard, ai.getOpponent())) {
|
if (card.isEquipment() && buffedcard.isCreature() && CombatUtil.canAttack(buffedcard, ComputerUtil.getOpponentFor(ai))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (card.isCreature()) {
|
if (card.isCreature()) {
|
||||||
if (buffedcard.hasKeyword("Soulbond") && !buffedcard.isPaired()) {
|
if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (buffedcard.hasKeyword("Evolve")) {
|
if (buffedcard.hasKeyword(Keyword.EVOLVE)) {
|
||||||
if (buffedcard.getNetPower() < card.getNetPower() || buffedcard.getNetToughness() < card.getNetToughness()) {
|
if (buffedcard.getNetPower() < card.getNetPower() || buffedcard.getNetToughness() < card.getNetToughness()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (card.hasKeyword("Soulbond") && buffedcard.isCreature() && !buffedcard.isPaired()) {
|
if (card.hasKeyword(Keyword.SOULBOND) && buffedcard.isCreature() && !buffedcard.isPaired()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // BuffedBy
|
} // BuffedBy
|
||||||
|
|
||||||
// get all cards the human controls with AntiBuffedBy
|
// get all cards the human controls with AntiBuffedBy
|
||||||
final CardCollectionView antibuffed = ai.getOpponent().getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
||||||
for (Card buffedcard : antibuffed) {
|
for (Card buffedcard : antibuffed) {
|
||||||
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
||||||
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
||||||
@@ -1033,11 +1054,11 @@ public class ComputerUtil {
|
|||||||
return ret;
|
return ret;
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, if life is possibly in danger, then this is fine.
|
// Otherwise, if life is possibly in danger, then this is fine.
|
||||||
Combat combat = new Combat(ai.getOpponent());
|
Combat combat = new Combat(ComputerUtil.getOpponentFor(ai));
|
||||||
CardCollectionView attackers = ai.getOpponent().getCreaturesInPlay();
|
CardCollectionView attackers = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
|
||||||
for (Card att : attackers) {
|
for (Card att : attackers) {
|
||||||
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
||||||
combat.addAttacker(att, att.getController().getOpponent());
|
combat.addAttacker(att, ComputerUtil.getOpponentFor(att.getController()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AiBlockController aiBlock = new AiBlockController(ai);
|
AiBlockController aiBlock = new AiBlockController(ai);
|
||||||
@@ -1101,7 +1122,7 @@ public class ComputerUtil {
|
|||||||
return (sa.getHostCard().isCreature()
|
return (sa.getHostCard().isCreature()
|
||||||
&& sa.getPayCosts().hasTapCost()
|
&& sa.getPayCosts().hasTapCost()
|
||||||
&& (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
&& (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
|| !ph.getNextTurn().equals(sa.getActivatingPlayer()))
|
&& !ph.getNextTurn().equals(sa.getActivatingPlayer()))
|
||||||
&& !sa.getHostCard().hasSVar("EndOfTurnLeavePlay")
|
&& !sa.getHostCard().hasSVar("EndOfTurnLeavePlay")
|
||||||
&& !sa.hasParam("ActivationPhases"));
|
&& !sa.hasParam("ActivationPhases"));
|
||||||
}
|
}
|
||||||
@@ -1111,6 +1132,10 @@ public class ComputerUtil {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final SpellAbility sub = sa.getSubAbility();
|
final SpellAbility sub = sa.getSubAbility();
|
||||||
|
|
||||||
|
if (source != null && "ALWAYS".equals(source.getSVar("PlayMain1"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Cipher spells
|
// Cipher spells
|
||||||
if (sub != null) {
|
if (sub != null) {
|
||||||
final ApiType api = sub.getApi();
|
final ApiType api = sub.getApi();
|
||||||
@@ -1145,7 +1170,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get all cards the human controls with AntiBuffedBy
|
// get all cards the human controls with AntiBuffedBy
|
||||||
final CardCollectionView antibuffed = ai.getOpponent().getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
||||||
for (Card buffedcard : antibuffed) {
|
for (Card buffedcard : antibuffed) {
|
||||||
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
||||||
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
||||||
@@ -1168,16 +1193,14 @@ public class ComputerUtil {
|
|||||||
int activations = sa.getRestrictions().getNumberTurnActivations();
|
int activations = sa.getRestrictions().getNumberTurnActivations();
|
||||||
|
|
||||||
if (sa.isTemporary()) {
|
if (sa.isTemporary()) {
|
||||||
final Random r = MyRandom.getRandom();
|
return MyRandom.getRandom().nextFloat() >= .95; // Abilities created by static abilities have no memory
|
||||||
return r.nextFloat() >= .95; // Abilities created by static abilities have no memory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activations < 10) { //10 activations per turn should still be acceptable
|
if (activations < 10) { //10 activations per turn should still be acceptable
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Random r = MyRandom.getRandom();
|
return MyRandom.getRandom().nextFloat() >= Math.pow(.95, activations);
|
||||||
return r.nextFloat() >= Math.pow(.95, activations);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean activateForCost(SpellAbility sa, final Player ai) {
|
public static boolean activateForCost(SpellAbility sa, final Player ai) {
|
||||||
@@ -1220,9 +1243,9 @@ public class ComputerUtil {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasACardGivingHaste(final Player ai) {
|
public static boolean hasACardGivingHaste(final Player ai, final boolean checkOpponentCards) {
|
||||||
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
final CardCollection all = new CardCollection(ai.getCardsIn(Lists.newArrayList(ZoneType.Battlefield, ZoneType.Command)));
|
||||||
|
|
||||||
// Special for Anger
|
// Special for Anger
|
||||||
if (!ai.getGame().isCardInPlay("Yixlid Jailer")
|
if (!ai.getGame().isCardInPlay("Yixlid Jailer")
|
||||||
&& !ai.getCardsIn(ZoneType.Graveyard, "Anger").isEmpty()
|
&& !ai.getCardsIn(ZoneType.Graveyard, "Anger").isEmpty()
|
||||||
@@ -1232,7 +1255,7 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
// Special for Odric
|
// Special for Odric
|
||||||
if (ai.isCardInPlay("Odric, Lunarch Marshal")
|
if (ai.isCardInPlay("Odric, Lunarch Marshal")
|
||||||
&& !CardLists.getKeyword(all, "Haste").isEmpty()) {
|
&& !CardLists.getKeyword(all, Keyword.HASTE).isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1288,6 +1311,28 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (checkOpponentCards) {
|
||||||
|
// Check if the opponents have any cards giving Haste to all creatures on the battlefield
|
||||||
|
CardCollection opp = new CardCollection();
|
||||||
|
opp.addAll(ai.getOpponents().getCardsIn(ZoneType.Battlefield));
|
||||||
|
opp.addAll(ai.getOpponents().getCardsIn(ZoneType.Command));
|
||||||
|
|
||||||
|
for (final Card c : opp) {
|
||||||
|
for (StaticAbility stAb : c.getStaticAbilities()) {
|
||||||
|
Map<String, String> params = stAb.getMapParams();
|
||||||
|
if ("Continuous".equals(params.get("Mode")) && params.containsKey("AddKeyword")
|
||||||
|
&& params.get("AddKeyword").contains("Haste")) {
|
||||||
|
|
||||||
|
final ArrayList<String> affected = Lists.newArrayList(params.get("Affected").split(","));
|
||||||
|
if (affected.contains("Creature")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1315,7 +1360,7 @@ public class ComputerUtil {
|
|||||||
int damage = 0;
|
int damage = 0;
|
||||||
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||||
all.addAll(ai.getCardsActivableInExternalZones(true));
|
all.addAll(ai.getCardsActivableInExternalZones(true));
|
||||||
all.addAll(ai.getCardsIn(ZoneType.Hand));
|
all.addAll(CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.not(Presets.PERMANENTS)));
|
||||||
|
|
||||||
for (final Card c : all) {
|
for (final Card c : all) {
|
||||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||||
@@ -1331,7 +1376,7 @@ public class ComputerUtil {
|
|||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final Player enemy = ai.getOpponent();
|
final Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||||
if (!sa.canTarget(enemy)) {
|
if (!sa.canTarget(enemy)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1340,7 +1385,26 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
damage = dmg;
|
damage = dmg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Triggered abilities
|
||||||
|
if (c.isCreature() && c.isInZone(ZoneType.Battlefield) && CombatUtil.canAttack(c)) {
|
||||||
|
for (final Trigger t : c.getTriggers()) {
|
||||||
|
if ("Attacks".equals(t.getParam("Mode")) && t.hasParam("Execute")) {
|
||||||
|
String exec = c.getSVar(t.getParam("Execute"));
|
||||||
|
if (!exec.isEmpty()) {
|
||||||
|
SpellAbility trigSa = AbilityFactory.getAbility(exec, c);
|
||||||
|
if (trigSa != null && trigSa.getApi() == ApiType.LoseLife
|
||||||
|
&& trigSa.getParamOrDefault("Defined", "").contains("Opponent")) {
|
||||||
|
trigSa.setHostCard(c);
|
||||||
|
damage += AbilityUtils.calculateAmount(trigSa.getHostCard(), trigSa.getParam("LifeAmount"), trigSa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1435,20 +1499,29 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
objects = canBeTargeted;
|
objects = canBeTargeted;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) {
|
SpellAbility saviorWithSubs = saviour;
|
||||||
toughness = saviour.hasParam("NumDef") ?
|
ApiType saviorWithSubsApi = saviorWithSubs == null ? null : saviorWithSubs.getApi();
|
||||||
AbilityUtils.calculateAmount(saviour.getHostCard(), saviour.getParam("NumDef"), saviour) : 0;
|
while (saviorWithSubs != null) {
|
||||||
final List<String> keywords = saviour.hasParam("KW") ?
|
ApiType curApi = saviorWithSubs.getApi();
|
||||||
Arrays.asList(saviour.getParam("KW").split(" & ")) : new ArrayList<String>();
|
if (curApi == ApiType.Pump || curApi == ApiType.PumpAll) {
|
||||||
if (keywords.contains("Indestructible")) {
|
toughness = saviorWithSubs.hasParam("NumDef") ?
|
||||||
grantIndestructible = true;
|
AbilityUtils.calculateAmount(saviorWithSubs.getHostCard(), saviorWithSubs.getParam("NumDef"), saviour) : 0;
|
||||||
}
|
final List<String> keywords = saviorWithSubs.hasParam("KW") ?
|
||||||
if (keywords.contains("Hexproof") || keywords.contains("Shroud")) {
|
Arrays.asList(saviorWithSubs.getParam("KW").split(" & ")) : new ArrayList<String>();
|
||||||
grantShroud = true;
|
if (keywords.contains("Indestructible")) {
|
||||||
|
grantIndestructible = true;
|
||||||
|
}
|
||||||
|
if (keywords.contains("Hexproof") || keywords.contains("Shroud")) {
|
||||||
|
grantShroud = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
// Consider pump in subabilities, e.g. Bristling Hydra hexproof subability
|
||||||
|
saviorWithSubs = saviorWithSubs.getSubAbility();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (saviourApi == ApiType.PutCounter || saviourApi == ApiType.PutCounterAll) {
|
if (saviourApi == ApiType.PutCounter || saviourApi == ApiType.PutCounterAll) {
|
||||||
if (saviour.getParam("CounterType").equals("P1P1")) {
|
if (saviour.getParam("CounterType").equals("P1P1")) {
|
||||||
toughness = AbilityUtils.calculateAmount(saviour.getHostCard(), saviour.getParam("CounterNum"), saviour);
|
toughness = AbilityUtils.calculateAmount(saviour.getHostCard(), saviour.getParam("CounterNum"), saviour);
|
||||||
@@ -1481,7 +1554,7 @@ public class ComputerUtil {
|
|||||||
final Card c = (Card) o;
|
final Card c = (Card) o;
|
||||||
|
|
||||||
// indestructible
|
// indestructible
|
||||||
if (c.hasKeyword("Indestructible")) {
|
if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1528,7 +1601,7 @@ public class ComputerUtil {
|
|||||||
} else if (o instanceof Player) {
|
} else if (o instanceof Player) {
|
||||||
final Player p = (Player) o;
|
final Player p = (Player) o;
|
||||||
|
|
||||||
if (source.hasKeyword("Infect")) {
|
if (source.hasKeyword(Keyword.INFECT)) {
|
||||||
if (ComputerUtilCombat.predictDamageTo(p, dmg, source, false) >= p.getPoisonCounters()) {
|
if (ComputerUtilCombat.predictDamageTo(p, dmg, source, false) >= p.getPoisonCounters()) {
|
||||||
threatened.add(p);
|
threatened.add(p);
|
||||||
}
|
}
|
||||||
@@ -1549,14 +1622,14 @@ public class ComputerUtil {
|
|||||||
if (o instanceof Card) {
|
if (o instanceof Card) {
|
||||||
final Card c = (Card) o;
|
final Card c = (Card) o;
|
||||||
final boolean canRemove = (c.getNetToughness() <= dmg)
|
final boolean canRemove = (c.getNetToughness() <= dmg)
|
||||||
|| (!c.hasKeyword("Indestructible") && c.getShieldCount() == 0 && (dmg >= ComputerUtilCombat.getDamageToKill(c)));
|
|| (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && c.getShieldCount() == 0 && (dmg >= ComputerUtilCombat.getDamageToKill(c)));
|
||||||
if (!canRemove) {
|
if (!canRemove) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) {
|
if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) {
|
||||||
final boolean cantSave = c.getNetToughness() + toughness <= dmg
|
final boolean cantSave = c.getNetToughness() + toughness <= dmg
|
||||||
|| (!c.hasKeyword("Indestructible") && c.getShieldCount() == 0 && !grantIndestructible
|
|| (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && c.getShieldCount() == 0 && !grantIndestructible
|
||||||
&& (dmg >= toughness + ComputerUtilCombat.getDamageToKill(c)));
|
&& (dmg >= toughness + ComputerUtilCombat.getDamageToKill(c)));
|
||||||
if (cantSave && (tgt == null || !grantShroud)) {
|
if (cantSave && (tgt == null || !grantShroud)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1588,14 +1661,15 @@ public class ComputerUtil {
|
|||||||
// Destroy => regeneration/bounce/shroud
|
// Destroy => regeneration/bounce/shroud
|
||||||
else if ((threatApi == ApiType.Destroy || threatApi == ApiType.DestroyAll)
|
else if ((threatApi == ApiType.Destroy || threatApi == ApiType.DestroyAll)
|
||||||
&& (((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll)
|
&& (((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll)
|
||||||
&& !topStack.hasParam("NoRegen")) || saviourApi == ApiType.ChangeZone
|
&& !topStack.hasParam("NoRegen")) || saviourApi == ApiType.ChangeZone
|
||||||
|| saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll
|
|| saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll
|
||||||
|| saviourApi == ApiType.Protection || saviourApi == null)) {
|
|| saviourApi == ApiType.Protection || saviourApi == null
|
||||||
|
|| saviorWithSubsApi == ApiType.Pump || saviorWithSubsApi == ApiType.PumpAll)) {
|
||||||
for (final Object o : objects) {
|
for (final Object o : objects) {
|
||||||
if (o instanceof Card) {
|
if (o instanceof Card) {
|
||||||
final Card c = (Card) o;
|
final Card c = (Card) o;
|
||||||
// indestructible
|
// indestructible
|
||||||
if (c.hasKeyword("Indestructible")) {
|
if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1604,7 +1678,9 @@ public class ComputerUtil {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) {
|
if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll
|
||||||
|
|| saviorWithSubsApi == ApiType.Pump
|
||||||
|
|| saviorWithSubsApi == ApiType.PumpAll) {
|
||||||
if ((tgt == null && !grantIndestructible)
|
if ((tgt == null && !grantIndestructible)
|
||||||
|| (!grantShroud && !grantIndestructible)) {
|
|| (!grantShroud && !grantIndestructible)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1857,6 +1933,27 @@ public class ComputerUtil {
|
|||||||
public static boolean scryWillMoveCardToBottomOfLibrary(Player player, Card c) {
|
public static boolean scryWillMoveCardToBottomOfLibrary(Player player, Card c) {
|
||||||
boolean bottom = false;
|
boolean bottom = false;
|
||||||
|
|
||||||
|
// AI profile-based toggles
|
||||||
|
int maxLandsToScryLandsToTop = 3;
|
||||||
|
int minLandsToScryLandsAway = 8;
|
||||||
|
int minCreatsToScryCreatsAway = 5;
|
||||||
|
int minCreatEvalThreshold = 160; // just a bit higher than a baseline 2/2 creature or a 1/1 mana dork
|
||||||
|
int lowCMCThreshold = 3;
|
||||||
|
int maxCreatsToScryLowCMCAway = 3;
|
||||||
|
boolean uncastablesToBottom = false;
|
||||||
|
int uncastableCMCThreshold = 1;
|
||||||
|
if (player.getController().isAI()) {
|
||||||
|
AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
||||||
|
maxLandsToScryLandsToTop = aic.getIntProperty(AiProps.SCRY_NUM_LANDS_TO_STILL_NEED_MORE);
|
||||||
|
minLandsToScryLandsAway = aic.getIntProperty(AiProps.SCRY_NUM_LANDS_TO_NOT_NEED_MORE);
|
||||||
|
minCreatsToScryCreatsAway = aic.getIntProperty(AiProps.SCRY_NUM_CREATURES_TO_NOT_NEED_SUBPAR_ONES);
|
||||||
|
minCreatEvalThreshold = aic.getIntProperty(AiProps.SCRY_EVALTHR_TO_SCRY_AWAY_LOWCMC_CREATURE);
|
||||||
|
lowCMCThreshold = aic.getIntProperty(AiProps.SCRY_EVALTHR_CMC_THRESHOLD);
|
||||||
|
maxCreatsToScryLowCMCAway = aic.getIntProperty(AiProps.SCRY_EVALTHR_CREATCOUNT_TO_SCRY_AWAY_LOWCMC);
|
||||||
|
uncastablesToBottom = aic.getBooleanProperty(AiProps.SCRY_IMMEDIATELY_UNCASTABLE_TO_BOTTOM);
|
||||||
|
uncastableCMCThreshold = aic.getIntProperty(AiProps.SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF);
|
||||||
|
}
|
||||||
|
|
||||||
CardCollectionView allCards = player.getAllCards();
|
CardCollectionView allCards = player.getAllCards();
|
||||||
CardCollectionView cardsInHand = player.getCardsIn(ZoneType.Hand);
|
CardCollectionView cardsInHand = player.getCardsIn(ZoneType.Hand);
|
||||||
CardCollectionView cardsOTB = player.getCardsIn(ZoneType.Battlefield);
|
CardCollectionView cardsOTB = player.getCardsIn(ZoneType.Battlefield);
|
||||||
@@ -1871,8 +1968,9 @@ public class ComputerUtil {
|
|||||||
CardCollectionView allCreatures = CardLists.filter(allCards, Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.isOwner(player)));
|
CardCollectionView allCreatures = CardLists.filter(allCards, Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.isOwner(player)));
|
||||||
int numCards = allCreatures.size();
|
int numCards = allCreatures.size();
|
||||||
|
|
||||||
if (landsOTB.size() < 3 && landsInHand.isEmpty()) {
|
if (landsOTB.size() < maxLandsToScryLandsToTop && landsInHand.isEmpty()) {
|
||||||
if ((!c.isLand() && !manaArts.contains(c.getName())) || c.getManaAbilities().isEmpty()) {
|
if ((!c.isLand() && !manaArts.contains(c.getName()))
|
||||||
|
|| (c.getManaAbilities().isEmpty() && !c.hasABasicLandType())) {
|
||||||
// scry away non-lands and non-manaproducing lands in situations when the land count
|
// scry away non-lands and non-manaproducing lands in situations when the land count
|
||||||
// on the battlefield is low, to try to improve the mana base early
|
// on the battlefield is low, to try to improve the mana base early
|
||||||
bottom = true;
|
bottom = true;
|
||||||
@@ -1880,7 +1978,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (c.isLand()) {
|
if (c.isLand()) {
|
||||||
if (landsOTB.size() >= 8) {
|
if (landsOTB.size() >= minLandsToScryLandsAway) {
|
||||||
// probably enough lands not to urgently need another one, so look for more gas instead
|
// probably enough lands not to urgently need another one, so look for more gas instead
|
||||||
bottom = true;
|
bottom = true;
|
||||||
} else if (landsInHand.size() >= Math.max(cardsInHand.size() / 2, 2)) {
|
} else if (landsInHand.size() >= Math.max(cardsInHand.size() / 2, 2)) {
|
||||||
@@ -1898,16 +1996,15 @@ public class ComputerUtil {
|
|||||||
} else if (c.isCreature()) {
|
} else if (c.isCreature()) {
|
||||||
CardCollection creaturesOTB = CardLists.filter(cardsOTB, CardPredicates.Presets.CREATURES);
|
CardCollection creaturesOTB = CardLists.filter(cardsOTB, CardPredicates.Presets.CREATURES);
|
||||||
int avgCreatureValue = numCards != 0 ? ComputerUtilCard.evaluateCreatureList(allCreatures) / numCards : 0;
|
int avgCreatureValue = numCards != 0 ? ComputerUtilCard.evaluateCreatureList(allCreatures) / numCards : 0;
|
||||||
int minCreatEvalThreshold = 160; // just a bit higher than a baseline 2/2 creature or a 1/1 mana dork
|
|
||||||
int maxControlledCMC = Aggregates.max(creaturesOTB, CardPredicates.Accessors.fnGetCmc);
|
int maxControlledCMC = Aggregates.max(creaturesOTB, CardPredicates.Accessors.fnGetCmc);
|
||||||
|
|
||||||
if (ComputerUtilCard.evaluateCreature(c) < avgCreatureValue) {
|
if (ComputerUtilCard.evaluateCreature(c) < avgCreatureValue) {
|
||||||
if (creaturesOTB.size() > 5) {
|
if (creaturesOTB.size() > minCreatsToScryCreatsAway) {
|
||||||
// if there are more than five creatures and the creature is question is below average for
|
// if there are more than five creatures and the creature is question is below average for
|
||||||
// the deck, scry it to the bottom
|
// the deck, scry it to the bottom
|
||||||
bottom = true;
|
bottom = true;
|
||||||
} else if (creaturesOTB.size() > 3 && c.getCMC() <= 3
|
} else if (creaturesOTB.size() > maxCreatsToScryLowCMCAway && c.getCMC() <= lowCMCThreshold
|
||||||
&& maxControlledCMC >= 4 && ComputerUtilCard.evaluateCreature(c) <= minCreatEvalThreshold) {
|
&& maxControlledCMC >= lowCMCThreshold + 1 && ComputerUtilCard.evaluateCreature(c) <= minCreatEvalThreshold) {
|
||||||
// if we are already at a stage when we have 4+ CMC creatures on the battlefield,
|
// if we are already at a stage when we have 4+ CMC creatures on the battlefield,
|
||||||
// probably worth it to scry away very low value creatures with low CMC
|
// probably worth it to scry away very low value creatures with low CMC
|
||||||
bottom = true;
|
bottom = true;
|
||||||
@@ -1915,6 +2012,15 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (uncastablesToBottom && !c.isLand()) {
|
||||||
|
int cmc = c.isSplitCard() ? Math.min(c.getCMC(Card.SplitCMCMode.LeftSplitCMC), c.getCMC(Card.SplitCMCMode.RightSplitCMC))
|
||||||
|
: c.getCMC();
|
||||||
|
int maxCastable = ComputerUtilMana.getAvailableManaEstimate(player, false) + landsInHand.size();
|
||||||
|
if (cmc - maxCastable >= uncastableCMCThreshold) {
|
||||||
|
bottom = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return bottom;
|
return bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2047,7 +2153,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (logic.equals("ChosenLandwalk")) {
|
else if (logic.equals("ChosenLandwalk")) {
|
||||||
for (Card c : ai.getOpponent().getLandsInPlay()) {
|
for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) {
|
||||||
for (String t : c.getType()) {
|
for (String t : c.getType()) {
|
||||||
if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) {
|
if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) {
|
||||||
chosen = t;
|
chosen = t;
|
||||||
@@ -2065,7 +2171,7 @@ public class ComputerUtil {
|
|||||||
else if (kindOfType.equals("Land")) {
|
else if (kindOfType.equals("Land")) {
|
||||||
if (logic != null) {
|
if (logic != null) {
|
||||||
if (logic.equals("ChosenLandwalk")) {
|
if (logic.equals("ChosenLandwalk")) {
|
||||||
for (Card c : ai.getOpponent().getLandsInPlay()) {
|
for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) {
|
||||||
for (String t : c.getType().getLandTypes()) {
|
for (String t : c.getType().getLandTypes()) {
|
||||||
if (!invalidTypes.contains(t)) {
|
if (!invalidTypes.contains(t)) {
|
||||||
chosen = t;
|
chosen = t;
|
||||||
@@ -2098,7 +2204,7 @@ public class ComputerUtil {
|
|||||||
case "Torture":
|
case "Torture":
|
||||||
return "Torture";
|
return "Torture";
|
||||||
case "GraceOrCondemnation":
|
case "GraceOrCondemnation":
|
||||||
return ai.getCreaturesInPlay().size() > ai.getOpponent().getCreaturesInPlay().size() ? "Grace"
|
return ai.getCreaturesInPlay().size() > ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size() ? "Grace"
|
||||||
: "Condemnation";
|
: "Condemnation";
|
||||||
case "CarnageOrHomage":
|
case "CarnageOrHomage":
|
||||||
CardCollection cardsInPlay = CardLists
|
CardCollection cardsInPlay = CardLists
|
||||||
@@ -2529,8 +2635,8 @@ public class ComputerUtil {
|
|||||||
// and also on Chronozoa
|
// and also on Chronozoa
|
||||||
|| (type == CounterType.TIME && (!c.isInPlay() || "Chronozoa".equals(c.getName())))
|
|| (type == CounterType.TIME && (!c.isInPlay() || "Chronozoa".equals(c.getName())))
|
||||||
|| type == CounterType.GOLD || type == CounterType.MUSIC || type == CounterType.PUPA
|
|| type == CounterType.GOLD || type == CounterType.MUSIC || type == CounterType.PUPA
|
||||||
|| type == CounterType.PARALYZATION || type == CounterType.SHELL || type == CounterType.SLEEP
|
|| type == CounterType.PARALYZATION || type == CounterType.SHELL || type == CounterType.SLEEP
|
||||||
|| type == CounterType.SLEIGHT || type == CounterType.WAGE;
|
|| type == CounterType.SLUMBER || type == CounterType.SLEIGHT || type == CounterType.WAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this countertypes has no effect
|
// this countertypes has no effect
|
||||||
@@ -2683,4 +2789,124 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public static final Player getOpponentFor(final Player player) {
|
||||||
|
// This method is deprecated and currently functions as a synonym for player.getWeakestOpponent
|
||||||
|
// until it can be replaced everywhere in the code.
|
||||||
|
|
||||||
|
// Consider replacing calls to this method either with a multiplayer-friendly determination of
|
||||||
|
// opponent that contextually makes the most sense, or with a direct call to player.getWeakestOpponent
|
||||||
|
// where that is applicable and makes sense from the point of view of multiplayer AI logic.
|
||||||
|
Player opponent = player.getWeakestOpponent();
|
||||||
|
if (opponent != null) {
|
||||||
|
return opponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException("No opponents left ingame for " + player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int countUsefulCreatures(Player p) {
|
||||||
|
CardCollection creats = p.getCreaturesInPlay();
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (Card c : creats) {
|
||||||
|
if (!ComputerUtilCard.isUselessCreature(p, c)) {
|
||||||
|
count ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPlayingReanimator(final Player ai) {
|
||||||
|
// TODO: either add SVars to other reanimator cards, or improve the prediction so that it avoids using a SVar
|
||||||
|
// at all but detects this effect from SA parameters (preferred, but difficult)
|
||||||
|
CardCollectionView inHand = ai.getCardsIn(ZoneType.Hand);
|
||||||
|
CardCollectionView inDeck = ai.getCardsIn(new ZoneType[] {ZoneType.Hand, ZoneType.Library});
|
||||||
|
|
||||||
|
Predicate<Card> markedAsReanimator = new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(Card card) {
|
||||||
|
return "true".equalsIgnoreCase(card.getSVar("IsReanimatorCard"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int numInHand = CardLists.filter(inHand, markedAsReanimator).size();
|
||||||
|
int numInDeck = CardLists.filter(inDeck, markedAsReanimator).size();
|
||||||
|
|
||||||
|
return numInHand > 0 || numInDeck >= 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CardCollection filterAITgts(SpellAbility sa, Player ai, CardCollection srcList, boolean alwaysStrict) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
if (source == null) { return srcList; }
|
||||||
|
|
||||||
|
if (sa.hasParam("AITgts")) {
|
||||||
|
CardCollection list;
|
||||||
|
if (sa.getParam("AITgts").equals("BetterThanSource")) {
|
||||||
|
int value = ComputerUtilCard.evaluateCreature(source);
|
||||||
|
if (source.isEnchanted()) {
|
||||||
|
for (Card enc : source.getEnchantedBy(false)) {
|
||||||
|
if (enc.getController().equals(ai)) {
|
||||||
|
value += 100; // is 100 per AI's own aura enough?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final int totalValue = value;
|
||||||
|
list = CardLists.filter(srcList, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
|
return ComputerUtilCard.evaluateCreature(c) > totalValue + 30;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
list = CardLists.getValidCards(srcList, sa.getParam("AITgts"), sa.getActivatingPlayer(), source);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!list.isEmpty() || sa.hasParam("AITgtsStrict") || alwaysStrict) {
|
||||||
|
return list;
|
||||||
|
} else {
|
||||||
|
return srcList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return srcList;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if AI life is in danger/serious danger based on next expected combat
|
||||||
|
// assuming a loss of "payment" life
|
||||||
|
// call this to determine if it's safe to use a life payment spell
|
||||||
|
// or trigger "emergency" strategies such as holding mana for Spike Weaver of Counterspell.
|
||||||
|
public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) {
|
||||||
|
Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||||
|
// test whether the human can kill the ai next turn
|
||||||
|
Combat combat = new Combat(opponent);
|
||||||
|
boolean containsAttacker = false;
|
||||||
|
for (Card att : opponent.getCreaturesInPlay()) {
|
||||||
|
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
||||||
|
combat.addAttacker(att, ai);
|
||||||
|
containsAttacker = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!containsAttacker) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
AiBlockController block = new AiBlockController(ai);
|
||||||
|
block.assignBlockersForCombat(combat);
|
||||||
|
|
||||||
|
// TODO predict other, noncombat sources of damage and add them to the "payment" variable.
|
||||||
|
// examples : Black Vise, The Rack, known direct damage spells in enemy hand, etc
|
||||||
|
// If added, might need a parameter to define whether we want to check all threats or combat threats.
|
||||||
|
|
||||||
|
if ((serious) && (ComputerUtilCombat.lifeInSeriousDanger(ai, combat, payment))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,137 +1,174 @@
|
|||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import forge.card.CardStateName;
|
|
||||||
import forge.game.Game;
|
import forge.card.CardStateName;
|
||||||
import forge.game.GameActionUtil;
|
import forge.game.Game;
|
||||||
import forge.game.card.Card;
|
import forge.game.GameActionUtil;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.player.Player;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
import forge.game.spellability.SpellAbilityStackInstance;
|
import forge.game.player.Player;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.spellability.SpellAbility;
|
||||||
import java.util.Iterator;
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
public class ComputerUtilAbility {
|
|
||||||
public static CardCollection getAvailableLandsToPlay(final Game game, final Player player) {
|
public class ComputerUtilAbility {
|
||||||
if (!game.getStack().isEmpty() || !game.getPhaseHandler().getPhase().isMain()) {
|
public static CardCollection getAvailableLandsToPlay(final Game game, final Player player) {
|
||||||
return null;
|
if (!game.getStack().isEmpty() || !game.getPhaseHandler().getPhase().isMain()) {
|
||||||
}
|
return null;
|
||||||
final CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
|
}
|
||||||
hand.addAll(player.getCardsIn(ZoneType.Exile));
|
final CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
|
||||||
CardCollection landList = CardLists.filter(hand, Presets.LANDS);
|
hand.addAll(player.getCardsIn(ZoneType.Exile));
|
||||||
|
CardCollection landList = CardLists.filter(hand, Presets.LANDS);
|
||||||
//filter out cards that can't be played
|
|
||||||
landList = CardLists.filter(landList, new Predicate<Card>() {
|
//filter out cards that can't be played
|
||||||
@Override
|
landList = CardLists.filter(landList, new Predicate<Card>() {
|
||||||
public boolean apply(final Card c) {
|
@Override
|
||||||
if (!c.getSVar("NeedsToPlay").isEmpty()) {
|
public boolean apply(final Card c) {
|
||||||
final String needsToPlay = c.getSVar("NeedsToPlay");
|
if (!c.getSVar("NeedsToPlay").isEmpty()) {
|
||||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay.split(","), c.getController(), c, null);
|
final String needsToPlay = c.getSVar("NeedsToPlay");
|
||||||
if (list.isEmpty()) {
|
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay.split(","), c.getController(), c, null);
|
||||||
return false;
|
if (list.isEmpty()) {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
return player.canPlayLand(c);
|
}
|
||||||
}
|
return player.canPlayLand(c);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
final CardCollection landsNotInHand = new CardCollection(player.getCardsIn(ZoneType.Graveyard));
|
|
||||||
landsNotInHand.addAll(game.getCardsIn(ZoneType.Exile));
|
final CardCollection landsNotInHand = new CardCollection(player.getCardsIn(ZoneType.Graveyard));
|
||||||
if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
|
landsNotInHand.addAll(game.getCardsIn(ZoneType.Exile));
|
||||||
landsNotInHand.add(player.getCardsIn(ZoneType.Library).get(0));
|
if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||||
}
|
landsNotInHand.add(player.getCardsIn(ZoneType.Library).get(0));
|
||||||
for (final Card crd : landsNotInHand) {
|
}
|
||||||
if (!(crd.isLand() || (crd.isFaceDown() && crd.getState(CardStateName.Original).getType().isLand()))) {
|
for (final Card crd : landsNotInHand) {
|
||||||
continue;
|
if (!(crd.isLand() || (crd.isFaceDown() && crd.getState(CardStateName.Original).getType().isLand()))) {
|
||||||
}
|
continue;
|
||||||
if (!crd.mayPlay(player).isEmpty()) {
|
}
|
||||||
landList.add(crd);
|
if (!crd.mayPlay(player).isEmpty()) {
|
||||||
}
|
landList.add(crd);
|
||||||
}
|
}
|
||||||
if (landList.isEmpty()) {
|
}
|
||||||
return null;
|
if (landList.isEmpty()) {
|
||||||
}
|
return null;
|
||||||
return landList;
|
}
|
||||||
}
|
return landList;
|
||||||
|
}
|
||||||
public static CardCollection getAvailableCards(final Game game, final Player player) {
|
|
||||||
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand));
|
public static CardCollection getAvailableCards(final Game game, final Player player) {
|
||||||
|
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand));
|
||||||
all.addAll(player.getCardsIn(ZoneType.Graveyard));
|
|
||||||
all.addAll(player.getCardsIn(ZoneType.Command));
|
all.addAll(player.getCardsIn(ZoneType.Graveyard));
|
||||||
if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
|
all.addAll(player.getCardsIn(ZoneType.Command));
|
||||||
all.add(player.getCardsIn(ZoneType.Library).get(0));
|
if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||||
}
|
all.add(player.getCardsIn(ZoneType.Library).get(0));
|
||||||
for(Player p : game.getPlayers()) {
|
}
|
||||||
all.addAll(p.getCardsIn(ZoneType.Exile));
|
for(Player p : game.getPlayers()) {
|
||||||
all.addAll(p.getCardsIn(ZoneType.Battlefield));
|
all.addAll(p.getCardsIn(ZoneType.Exile));
|
||||||
}
|
all.addAll(p.getCardsIn(ZoneType.Battlefield));
|
||||||
return all;
|
}
|
||||||
}
|
return all;
|
||||||
|
}
|
||||||
public static List<SpellAbility> getSpellAbilities(final CardCollectionView l, final Player player) {
|
|
||||||
final List<SpellAbility> spellAbilities = new ArrayList<SpellAbility>();
|
public static List<SpellAbility> getSpellAbilities(final CardCollectionView l, final Player player) {
|
||||||
for (final Card c : l) {
|
final List<SpellAbility> spellAbilities = Lists.newArrayList();
|
||||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
for (final Card c : l) {
|
||||||
spellAbilities.add(sa);
|
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||||
}
|
spellAbilities.add(sa);
|
||||||
if (c.isFaceDown() && c.isInZone(ZoneType.Exile) && !c.mayPlay(player).isEmpty()) {
|
}
|
||||||
for (final SpellAbility sa : c.getState(CardStateName.Original).getSpellAbilities()) {
|
if (c.isFaceDown() && c.isInZone(ZoneType.Exile) && !c.mayPlay(player).isEmpty()) {
|
||||||
spellAbilities.add(sa);
|
for (final SpellAbility sa : c.getState(CardStateName.Original).getSpellAbilities()) {
|
||||||
}
|
spellAbilities.add(sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return spellAbilities;
|
}
|
||||||
}
|
return spellAbilities;
|
||||||
|
}
|
||||||
public static List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList, final Player player) {
|
|
||||||
final List<SpellAbility> newAbilities = new ArrayList<SpellAbility>();
|
public static List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList, final Player player) {
|
||||||
for (SpellAbility sa : originList) {
|
final List<SpellAbility> newAbilities = Lists.newArrayList();
|
||||||
sa.setActivatingPlayer(player);
|
for (SpellAbility sa : originList) {
|
||||||
//add alternative costs as additional spell abilities
|
sa.setActivatingPlayer(player);
|
||||||
newAbilities.add(sa);
|
//add alternative costs as additional spell abilities
|
||||||
newAbilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
|
newAbilities.add(sa);
|
||||||
}
|
newAbilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
|
||||||
|
}
|
||||||
final List<SpellAbility> result = new ArrayList<SpellAbility>();
|
|
||||||
for (SpellAbility sa : newAbilities) {
|
final List<SpellAbility> result = Lists.newArrayList();
|
||||||
sa.setActivatingPlayer(player);
|
for (SpellAbility sa : newAbilities) {
|
||||||
result.addAll(GameActionUtil.getOptionalCosts(sa));
|
sa.setActivatingPlayer(player);
|
||||||
}
|
result.addAll(GameActionUtil.getOptionalCosts(sa));
|
||||||
return result;
|
}
|
||||||
}
|
return result;
|
||||||
|
}
|
||||||
public static SpellAbility getTopSpellAbilityOnStack(Game game, SpellAbility sa) {
|
|
||||||
Iterator<SpellAbilityStackInstance> it = game.getStack().iterator();
|
public static SpellAbility getTopSpellAbilityOnStack(Game game, SpellAbility sa) {
|
||||||
|
Iterator<SpellAbilityStackInstance> it = game.getStack().iterator();
|
||||||
if (!it.hasNext()) {
|
|
||||||
return null;
|
if (!it.hasNext()) {
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
SpellAbility tgtSA = it.next().getSpellAbility(true);
|
|
||||||
// Grab the topmost spellability that isn't this SA and use that for comparisons
|
SpellAbility tgtSA = it.next().getSpellAbility(true);
|
||||||
if (sa.equals(tgtSA) && game.getStack().size() > 1) {
|
// Grab the topmost spellability that isn't this SA and use that for comparisons
|
||||||
if (!it.hasNext()) {
|
if (sa.equals(tgtSA) && game.getStack().size() > 1) {
|
||||||
return null;
|
if (!it.hasNext()) {
|
||||||
}
|
return null;
|
||||||
tgtSA = it.next().getSpellAbility(true);
|
}
|
||||||
}
|
tgtSA = it.next().getSpellAbility(true);
|
||||||
return tgtSA;
|
}
|
||||||
}
|
return tgtSA;
|
||||||
|
}
|
||||||
public static Card getAbilitySource(SpellAbility sa) {
|
|
||||||
return sa.getOriginalHost() != null ? sa.getOriginalHost() : sa.getHostCard();
|
public static Card getAbilitySource(SpellAbility sa) {
|
||||||
}
|
return sa.getOriginalHost() != null ? sa.getOriginalHost() : sa.getHostCard();
|
||||||
|
}
|
||||||
public static String getAbilitySourceName(SpellAbility sa) {
|
|
||||||
return sa.getOriginalHost() != null ? sa.getOriginalHost().getName() : sa.getHostCard() != null ? sa.getHostCard().getName() : "";
|
public static String getAbilitySourceName(SpellAbility sa) {
|
||||||
}
|
final Card c = getAbilitySource(sa);
|
||||||
}
|
return c != null ? c.getName() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CardCollection getCardsTargetedWithApi(Player ai, CardCollection cardList, SpellAbility sa, ApiType api) {
|
||||||
|
// Returns a collection of cards which have already been targeted with the given API either in the parent ability,
|
||||||
|
// in the sub ability, or by something on stack. If "sa" is specified, the parent and sub abilities of this SA will
|
||||||
|
// be checked for targets. If "sa" is null, only the stack instances will be checked.
|
||||||
|
CardCollection targeted = new CardCollection();
|
||||||
|
if (sa != null) {
|
||||||
|
SpellAbility saSub = sa.getRootAbility();
|
||||||
|
while (saSub != null) {
|
||||||
|
if (saSub.getApi() == api && saSub.getTargets() != null) {
|
||||||
|
for (Card c : cardList) {
|
||||||
|
if (saSub.getTargets().getTargetCards().contains(c)) {
|
||||||
|
// Was already targeted with this API in a parent or sub SA
|
||||||
|
targeted.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
saSub = saSub.getSubAbility();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (SpellAbilityStackInstance si : ai.getGame().getStack()) {
|
||||||
|
SpellAbility ab = si.getSpellAbility(false);
|
||||||
|
if (ab != null && ab.getApi() == api && si.getTargetChoices() != null) {
|
||||||
|
for (Card c : cardList) {
|
||||||
|
// TODO: somehow ensure that the detected SA won't be countered
|
||||||
|
if (si.getTargetChoices().getTargetCards().contains(c)) {
|
||||||
|
// Was already targeted by a spell ability instance on stack
|
||||||
|
targeted.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return targeted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,215 +1,271 @@
|
|||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
|
|
||||||
import forge.game.card.Card;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.ability.ApiType;
|
||||||
|
import forge.game.card.Card;
|
||||||
public class CreatureEvaluator implements Function<Card, Integer> {
|
import forge.game.card.CounterType;
|
||||||
protected int getEffectivePower(final Card c) {
|
import forge.game.cost.CostPayEnergy;
|
||||||
return c.getNetCombatDamage();
|
import forge.game.keyword.Keyword;
|
||||||
}
|
import forge.game.keyword.KeywordInterface;
|
||||||
protected int getEffectiveToughness(final Card c) {
|
import forge.game.spellability.SpellAbility;
|
||||||
return c.getNetToughness();
|
|
||||||
}
|
public class CreatureEvaluator implements Function<Card, Integer> {
|
||||||
|
protected int getEffectivePower(final Card c) {
|
||||||
@Override
|
return c.getNetCombatDamage();
|
||||||
public Integer apply(Card c) {
|
}
|
||||||
return evaluateCreature(c);
|
protected int getEffectiveToughness(final Card c) {
|
||||||
}
|
return c.getNetToughness();
|
||||||
|
}
|
||||||
public int evaluateCreature(final Card c) {
|
|
||||||
int value = 80;
|
@Override
|
||||||
if (!c.isToken()) {
|
public Integer apply(Card c) {
|
||||||
value += addValue(20, "non-token"); // tokens should be worth less than actual cards
|
return evaluateCreature(c);
|
||||||
}
|
}
|
||||||
int power = getEffectivePower(c);
|
|
||||||
final int toughness = getEffectiveToughness(c);
|
public int evaluateCreature(final Card c) {
|
||||||
for (String keyword : c.getKeywords()) {
|
return evaluateCreature(c, true, true);
|
||||||
if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.")
|
}
|
||||||
|| keyword.equals("Prevent all damage that would be dealt by CARDNAME.")
|
|
||||||
|| keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|
public int evaluateCreature(final Card c, final boolean considerPT, final boolean considerCMC) {
|
||||||
|| keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
|
int value = 80;
|
||||||
power = 0;
|
if (!c.isToken()) {
|
||||||
break;
|
value += addValue(20, "non-token"); // tokens should be worth less than actual cards
|
||||||
}
|
}
|
||||||
}
|
int power = getEffectivePower(c);
|
||||||
value += addValue(power * 15, "power");
|
final int toughness = getEffectiveToughness(c);
|
||||||
value += addValue(toughness * 10, "toughness: " + toughness);
|
for (KeywordInterface kw : c.getKeywords()) {
|
||||||
value += addValue(c.getCMC() * 5, "cmc");
|
String keyword = kw.getOriginal();
|
||||||
|
if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.")
|
||||||
// Evasion keywords
|
|| keyword.equals("Prevent all damage that would be dealt by CARDNAME.")
|
||||||
if (c.hasKeyword("Flying")) {
|
|| keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|
||||||
value += addValue(power * 10, "flying");
|
|| keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
|
||||||
}
|
power = 0;
|
||||||
if (c.hasKeyword("Horsemanship")) {
|
break;
|
||||||
value += addValue(power * 10, "horses");
|
}
|
||||||
}
|
}
|
||||||
if (c.hasKeyword("Unblockable")) {
|
if (considerPT) {
|
||||||
value += addValue(power * 10, "unblockable");
|
value += addValue(power * 15, "power");
|
||||||
} else {
|
value += addValue(toughness * 10, "toughness: " + toughness);
|
||||||
if (c.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
}
|
||||||
value += addValue(power * 6, "thorns");
|
if (considerCMC) {
|
||||||
}
|
value += addValue(c.getCMC() * 5, "cmc");
|
||||||
if (c.hasKeyword("Fear")) {
|
}
|
||||||
value += addValue(power * 6, "fear");
|
|
||||||
}
|
// Evasion keywords
|
||||||
if (c.hasKeyword("Intimidate")) {
|
if (c.hasKeyword(Keyword.FLYING)) {
|
||||||
value += addValue(power * 6, "intimidate");
|
value += addValue(power * 10, "flying");
|
||||||
}
|
}
|
||||||
if (c.hasStartOfKeyword("Menace")) {
|
if (c.hasKeyword(Keyword.HORSEMANSHIP)) {
|
||||||
value += addValue(power * 4, "menace");
|
value += addValue(power * 10, "horses");
|
||||||
}
|
}
|
||||||
if (c.hasStartOfKeyword("CantBeBlockedBy")) {
|
if (c.hasKeyword("Unblockable")) {
|
||||||
value += addValue(power * 3, "block-restrict");
|
value += addValue(power * 10, "unblockable");
|
||||||
}
|
} else {
|
||||||
}
|
if (c.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
||||||
|
value += addValue(power * 6, "thorns");
|
||||||
// Other good keywords
|
}
|
||||||
if (power > 0) {
|
if (c.hasKeyword(Keyword.FEAR)) {
|
||||||
if (c.hasKeyword("Double Strike")) {
|
value += addValue(power * 6, "fear");
|
||||||
value += addValue(10 + (power * 15), "ds");
|
}
|
||||||
} else if (c.hasKeyword("First Strike")) {
|
if (c.hasKeyword(Keyword.INTIMIDATE)) {
|
||||||
value += addValue(10 + (power * 5), "fs");
|
value += addValue(power * 6, "intimidate");
|
||||||
}
|
}
|
||||||
if (c.hasKeyword("Deathtouch")) {
|
if (c.hasKeyword(Keyword.MENACE)) {
|
||||||
value += addValue(25, "dt");
|
value += addValue(power * 4, "menace");
|
||||||
}
|
}
|
||||||
if (c.hasKeyword("Lifelink")) {
|
if (c.hasStartOfKeyword("CantBeBlockedBy")) {
|
||||||
value += addValue(power * 10, "lifelink");
|
value += addValue(power * 3, "block-restrict");
|
||||||
}
|
}
|
||||||
if (power > 1 && c.hasKeyword("Trample")) {
|
}
|
||||||
value += addValue((power - 1) * 5, "trample");
|
|
||||||
}
|
// Other good keywords
|
||||||
if (c.hasKeyword("Vigilance")) {
|
if (power > 0) {
|
||||||
value += addValue((power * 5) + (toughness * 5), "vigilance");
|
if (c.hasKeyword(Keyword.DOUBLE_STRIKE)) {
|
||||||
}
|
value += addValue(10 + (power * 15), "ds");
|
||||||
if (c.hasKeyword("Wither")) {
|
} else if (c.hasKeyword(Keyword.FIRST_STRIKE)) {
|
||||||
value += addValue(power * 10, "Wither");
|
value += addValue(10 + (power * 5), "fs");
|
||||||
}
|
}
|
||||||
if (c.hasKeyword("Infect")) {
|
if (c.hasKeyword(Keyword.DEATHTOUCH)) {
|
||||||
value += addValue(power * 15, "infect");
|
value += addValue(25, "dt");
|
||||||
}
|
}
|
||||||
value += addValue(c.getKeywordMagnitude("Rampage"), "rampage");
|
if (c.hasKeyword(Keyword.LIFELINK)) {
|
||||||
}
|
value += addValue(power * 10, "lifelink");
|
||||||
|
}
|
||||||
value += addValue(c.getKeywordMagnitude("Bushido") * 16, "bushido");
|
if (power > 1 && c.hasKeyword(Keyword.TRAMPLE)) {
|
||||||
value += addValue(c.getAmountOfKeyword("Flanking") * 15, "flanking");
|
value += addValue((power - 1) * 5, "trample");
|
||||||
value += addValue(c.getAmountOfKeyword("Exalted") * 15, "exalted");
|
}
|
||||||
value += addValue(c.getKeywordMagnitude("Annihilator") * 50, "eldrazi");
|
if (c.hasKeyword(Keyword.VIGILANCE)) {
|
||||||
value += addValue(c.getKeywordMagnitude("Absorb") * 11, "absorb");
|
value += addValue((power * 5) + (toughness * 5), "vigilance");
|
||||||
|
}
|
||||||
// Defensive Keywords
|
if (c.hasKeyword(Keyword.WITHER)) {
|
||||||
if (c.hasKeyword("Reach") && !c.hasKeyword("Flying")) {
|
value += addValue(power * 10, "Wither");
|
||||||
value += addValue(5, "reach");
|
}
|
||||||
}
|
if (c.hasKeyword(Keyword.INFECT)) {
|
||||||
if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
|
value += addValue(power * 15, "infect");
|
||||||
value += addValue(3, "shadow-block");
|
}
|
||||||
}
|
value += addValue(c.getKeywordMagnitude(Keyword.RAMPAGE), "rampage");
|
||||||
|
value += addValue(c.getKeywordMagnitude(Keyword.AFFLICT) * 5, "afflict");
|
||||||
// Protection
|
}
|
||||||
if (c.hasKeyword("Indestructible")) {
|
|
||||||
value += addValue(70, "darksteel");
|
value += addValue(c.getKeywordMagnitude(Keyword.BUSHIDO) * 16, "bushido");
|
||||||
}
|
value += addValue(c.getAmountOfKeyword(Keyword.FLANKING) * 15, "flanking");
|
||||||
if (c.hasKeyword("Prevent all damage that would be dealt to CARDNAME.")) {
|
value += addValue(c.getAmountOfKeyword(Keyword.EXALTED) * 15, "exalted");
|
||||||
value += addValue(60, "cho-manno");
|
value += addValue(c.getKeywordMagnitude(Keyword.ANNIHILATOR) * 50, "eldrazi");
|
||||||
} else if (c.hasKeyword("Prevent all combat damage that would be dealt to CARDNAME.")) {
|
value += addValue(c.getKeywordMagnitude(Keyword.ABSORB) * 11, "absorb");
|
||||||
value += addValue(50, "fogbank");
|
|
||||||
}
|
// Keywords that may produce temporary or permanent buffs over time
|
||||||
if (c.hasKeyword("Hexproof")) {
|
if (c.hasKeyword(Keyword.PROWESS)) {
|
||||||
value += addValue(35, "hexproof");
|
value += addValue(5, "prowess");
|
||||||
} else if (c.hasKeyword("Shroud")) {
|
}
|
||||||
value += addValue(30, "shroud");
|
if (c.hasKeyword(Keyword.OUTLAST)) {
|
||||||
}
|
value += addValue(10, "outlast");
|
||||||
if (c.hasStartOfKeyword("Protection")) {
|
}
|
||||||
value += addValue(20, "protection");
|
|
||||||
}
|
// Defensive Keywords
|
||||||
if (c.hasStartOfKeyword("PreventAllDamageBy")) {
|
if (c.hasKeyword(Keyword.REACH) && !c.hasKeyword(Keyword.FLYING)) {
|
||||||
value += addValue(10, "prevent-dmg");
|
value += addValue(5, "reach");
|
||||||
}
|
}
|
||||||
|
if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
|
||||||
// Bad keywords
|
value += addValue(3, "shadow-block");
|
||||||
if (c.hasKeyword("Defender") || c.hasKeyword("CARDNAME can't attack.")) {
|
}
|
||||||
value -= subValue((power * 9) + 40, "defender");
|
|
||||||
} else if (c.getSVar("SacrificeEndCombat").equals("True")) {
|
// Protection
|
||||||
value -= subValue(40, "sac-end");
|
if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
|
||||||
}
|
value += addValue(70, "darksteel");
|
||||||
if (c.hasKeyword("CARDNAME can't block.")) {
|
}
|
||||||
value -= subValue(10, "cant-block");
|
if (c.hasKeyword("Prevent all damage that would be dealt to CARDNAME.")) {
|
||||||
} else if (c.hasKeyword("CARDNAME attacks each turn if able.")
|
value += addValue(60, "cho-manno");
|
||||||
|| c.hasKeyword("CARDNAME attacks each combat if able.")) {
|
} else if (c.hasKeyword("Prevent all combat damage that would be dealt to CARDNAME.")) {
|
||||||
value -= subValue(10, "must-attack");
|
value += addValue(50, "fogbank");
|
||||||
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
|
}
|
||||||
value -= subValue(10, "must-attack-player");
|
if (c.hasKeyword(Keyword.HEXPROOF)) {
|
||||||
} else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
|
value += addValue(35, "hexproof");
|
||||||
value -= subValue(toughness * 5, "reverse-reach");
|
} else if (c.hasKeyword(Keyword.SHROUD)) {
|
||||||
}
|
value += addValue(30, "shroud");
|
||||||
|
}
|
||||||
if (c.hasSVar("DestroyWhenDamaged")) {
|
if (c.hasKeyword(Keyword.PROTECTION)) {
|
||||||
value -= subValue((toughness - 1) * 9, "dies-to-dmg");
|
value += addValue(20, "protection");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c.hasKeyword("CARDNAME can't attack or block.")) {
|
// Bad keywords
|
||||||
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
|
if (c.hasKeyword(Keyword.DEFENDER) || c.hasKeyword("CARDNAME can't attack.")) {
|
||||||
}
|
value -= subValue((power * 9) + 40, "defender");
|
||||||
if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
|
} else if (c.getSVar("SacrificeEndCombat").equals("True")) {
|
||||||
if (c.isTapped()) {
|
value -= subValue(40, "sac-end");
|
||||||
value = addValue(50 + (c.getCMC() * 5), "tapped-useless"); // reset everything - useless
|
}
|
||||||
} else {
|
if (c.hasKeyword("CARDNAME can't block.")) {
|
||||||
value -= subValue(50, "doesnt-untap");
|
value -= subValue(10, "cant-block");
|
||||||
}
|
} else if (c.hasKeyword("CARDNAME attacks each turn if able.")
|
||||||
}
|
|| c.hasKeyword("CARDNAME attacks each combat if able.")) {
|
||||||
if (c.hasSVar("EndOfTurnLeavePlay")) {
|
value -= subValue(10, "must-attack");
|
||||||
value -= subValue(50, "eot-leaves");
|
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
|
||||||
} else if (c.hasStartOfKeyword("Cumulative upkeep")) {
|
value -= subValue(10, "must-attack-player");
|
||||||
value -= subValue(30, "cupkeep");
|
} else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
|
||||||
} else if (c.hasStartOfKeyword("UpkeepCost")) {
|
value -= subValue(toughness * 5, "reverse-reach");
|
||||||
value -= subValue(20, "sac-unless");
|
}
|
||||||
} else if (c.hasStartOfKeyword("Echo") && c.cameUnderControlSinceLastUpkeep()) {
|
|
||||||
value -= subValue(10, "echo-unpaid");
|
if (c.hasSVar("DestroyWhenDamaged")) {
|
||||||
}
|
value -= subValue((toughness - 1) * 9, "dies-to-dmg");
|
||||||
|
}
|
||||||
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
|
|
||||||
value -= subValue(20, "upkeep-dmg");
|
if (c.hasKeyword("CARDNAME can't attack or block.")) {
|
||||||
}
|
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
|
||||||
if (c.hasStartOfKeyword("Fading")) {
|
}
|
||||||
value -= subValue(20, "fading");
|
if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
|
||||||
}
|
if (c.isTapped()) {
|
||||||
if (c.hasStartOfKeyword("Vanishing")) {
|
value = addValue(50 + (c.getCMC() * 5), "tapped-useless"); // reset everything - useless
|
||||||
value -= subValue(20, "vanishing");
|
} else {
|
||||||
}
|
value -= subValue(50, "doesnt-untap");
|
||||||
if (c.getSVar("Targeting").equals("Dies")) {
|
}
|
||||||
value -= subValue(25, "dies");
|
}
|
||||||
}
|
if (c.hasSVar("EndOfTurnLeavePlay")) {
|
||||||
|
value -= subValue(50, "eot-leaves");
|
||||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
} else if (c.hasStartOfKeyword("Cumulative upkeep")) {
|
||||||
if (sa.isAbility()) {
|
value -= subValue(30, "cupkeep");
|
||||||
value += addValue(10, "sa: " + sa);
|
} else if (c.hasStartOfKeyword("UpkeepCost")) {
|
||||||
}
|
value -= subValue(20, "sac-unless");
|
||||||
}
|
} else if (c.hasKeyword(Keyword.ECHO) && c.cameUnderControlSinceLastUpkeep()) {
|
||||||
if (!c.getManaAbilities().isEmpty()) {
|
value -= subValue(10, "echo-unpaid");
|
||||||
value += addValue(10, "manadork");
|
}
|
||||||
}
|
|
||||||
|
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
|
||||||
if (c.isUntapped()) {
|
value -= subValue(20, "upkeep-dmg");
|
||||||
value += addValue(1, "untapped");
|
}
|
||||||
}
|
if (c.hasKeyword(Keyword.FADING)) {
|
||||||
|
value -= subValue(20, "fading");
|
||||||
// paired creatures are more valuable because they grant a bonus to the other creature
|
}
|
||||||
if (c.isPaired()) {
|
if (c.hasKeyword(Keyword.VANISHING)) {
|
||||||
value += addValue(14, "paired");
|
value -= subValue(20, "vanishing");
|
||||||
}
|
}
|
||||||
|
if (c.getSVar("Targeting").equals("Dies")) {
|
||||||
if (!c.getEncodedCards().isEmpty()) {
|
value -= subValue(25, "dies");
|
||||||
value += addValue(24, "encoded");
|
}
|
||||||
}
|
|
||||||
return value;
|
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||||
}
|
if (sa.isAbility()) {
|
||||||
|
value += addValue(evaluateSpellAbility(sa), "sa: " + sa);
|
||||||
protected int addValue(int value, String text) {
|
}
|
||||||
return value;
|
}
|
||||||
}
|
if (!c.getManaAbilities().isEmpty()) {
|
||||||
protected int subValue(int value, String text) {
|
value += addValue(10, "manadork");
|
||||||
return -addValue(-value, text);
|
}
|
||||||
}
|
|
||||||
}
|
if (c.isUntapped()) {
|
||||||
|
value += addValue(1, "untapped");
|
||||||
|
}
|
||||||
|
|
||||||
|
// paired creatures are more valuable because they grant a bonus to the other creature
|
||||||
|
if (c.isPaired()) {
|
||||||
|
value += addValue(14, "paired");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!c.getEncodedCards().isEmpty()) {
|
||||||
|
value += addValue(24, "encoded");
|
||||||
|
}
|
||||||
|
|
||||||
|
// card-specific evaluation modifier
|
||||||
|
if (c.hasSVar("AIEvaluationModifier")) {
|
||||||
|
int mod = AbilityUtils.calculateAmount(c, c.getSVar("AIEvaluationModifier"), null);
|
||||||
|
value += mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int evaluateSpellAbility(SpellAbility sa) {
|
||||||
|
// Pump abilities
|
||||||
|
if (sa.getApi() == ApiType.Pump) {
|
||||||
|
// Pump abilities that grant +X/+X to the card
|
||||||
|
if ("+X".equals(sa.getParam("NumAtt"))
|
||||||
|
&& "+X".equals(sa.getParam("NumDef"))
|
||||||
|
&& !sa.usesTargeting()
|
||||||
|
&& (!sa.hasParam("Defined") || "Self".equals(sa.getParam("Defined")))) {
|
||||||
|
if (sa.getPayCosts() != null && sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||||
|
// Electrostatic Pummeler, can be expanded for similar cards
|
||||||
|
int initPower = getEffectivePower(sa.getHostCard());
|
||||||
|
int pumpedPower = initPower;
|
||||||
|
int energy = sa.getHostCard().getController().getCounters(CounterType.ENERGY);
|
||||||
|
if (energy > 0) {
|
||||||
|
int numActivations = energy / 3;
|
||||||
|
for (int i = 0; i < numActivations; i++) {
|
||||||
|
pumpedPower *= 2;
|
||||||
|
}
|
||||||
|
return (pumpedPower - initPower) * 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// default value
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int addValue(int value, String text) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
protected int subValue(int value, String text) {
|
||||||
|
return -addValue(-value, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,72 +1,71 @@
|
|||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import forge.AIOption;
|
import forge.LobbyPlayer;
|
||||||
import forge.LobbyPlayer;
|
import forge.game.Game;
|
||||||
import forge.game.Game;
|
import forge.game.player.IGameEntitiesFactory;
|
||||||
import forge.game.player.IGameEntitiesFactory;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.PlayerController;
|
||||||
import forge.game.player.PlayerController;
|
|
||||||
|
public class LobbyPlayerAi extends LobbyPlayer implements IGameEntitiesFactory {
|
||||||
public class LobbyPlayerAi extends LobbyPlayer implements IGameEntitiesFactory {
|
|
||||||
|
private String aiProfile = "";
|
||||||
private String aiProfile = "";
|
private boolean rotateProfileEachGame;
|
||||||
private boolean rotateProfileEachGame;
|
private boolean allowCheatShuffle;
|
||||||
private boolean allowCheatShuffle;
|
private boolean useSimulation;
|
||||||
private boolean useSimulation;
|
|
||||||
|
public LobbyPlayerAi(String name, Set<AIOption> options) {
|
||||||
public LobbyPlayerAi(String name, Set<AIOption> options) {
|
super(name);
|
||||||
super(name);
|
if (options != null && options.contains(AIOption.USE_SIMULATION)) {
|
||||||
if (options != null && options.contains(AIOption.USE_SIMULATION)) {
|
this.useSimulation = true;
|
||||||
this.useSimulation = true;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public boolean isAllowCheatShuffle() {
|
||||||
public boolean isAllowCheatShuffle() {
|
return allowCheatShuffle;
|
||||||
return allowCheatShuffle;
|
}
|
||||||
}
|
|
||||||
|
public void setAllowCheatShuffle(boolean allowCheatShuffle) {
|
||||||
public void setAllowCheatShuffle(boolean allowCheatShuffle) {
|
this.allowCheatShuffle = allowCheatShuffle;
|
||||||
this.allowCheatShuffle = allowCheatShuffle;
|
}
|
||||||
}
|
|
||||||
|
public void setAiProfile(String profileName) {
|
||||||
public void setAiProfile(String profileName) {
|
aiProfile = profileName;
|
||||||
aiProfile = profileName;
|
}
|
||||||
}
|
|
||||||
|
public String getAiProfile() {
|
||||||
public String getAiProfile() {
|
return aiProfile;
|
||||||
return aiProfile;
|
}
|
||||||
}
|
|
||||||
|
public void setRotateProfileEachGame(boolean rotateProfileEachGame) {
|
||||||
public void setRotateProfileEachGame(boolean rotateProfileEachGame) {
|
this.rotateProfileEachGame = rotateProfileEachGame;
|
||||||
this.rotateProfileEachGame = rotateProfileEachGame;
|
}
|
||||||
}
|
|
||||||
|
private PlayerControllerAi createControllerFor(Player ai) {
|
||||||
private PlayerControllerAi createControllerFor(Player ai) {
|
PlayerControllerAi result = new PlayerControllerAi(ai.getGame(), ai, this);
|
||||||
PlayerControllerAi result = new PlayerControllerAi(ai.getGame(), ai, this);
|
result.setUseSimulation(useSimulation);
|
||||||
result.setUseSimulation(useSimulation);
|
result.allowCheatShuffle(allowCheatShuffle);
|
||||||
result.allowCheatShuffle(allowCheatShuffle);
|
return result;
|
||||||
return result;
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
public PlayerController createMindSlaveController(Player master, Player slave) {
|
||||||
public PlayerController createMindSlaveController(Player master, Player slave) {
|
return createControllerFor(slave);
|
||||||
return createControllerFor(slave);
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
public Player createIngamePlayer(Game game, final int id) {
|
||||||
public Player createIngamePlayer(Game game, final int id) {
|
Player ai = new Player(getName(), game, id);
|
||||||
Player ai = new Player(getName(), game, id);
|
ai.setFirstController(createControllerFor(ai));
|
||||||
ai.setFirstController(createControllerFor(ai));
|
|
||||||
|
if (rotateProfileEachGame) {
|
||||||
if (rotateProfileEachGame) {
|
setAiProfile(AiProfileUtil.getRandomProfile());
|
||||||
setAiProfile(AiProfileUtil.getRandomProfile());
|
/*System.out.println(String.format("AI profile %s was chosen for the lobby player %s.", getAiProfile(), getName()));*/
|
||||||
/*System.out.println(String.format("AI profile %s was chosen for the lobby player %s.", getAiProfile(), getName()));*/
|
}
|
||||||
}
|
return ai;
|
||||||
return ai;
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
public void hear(LobbyPlayer player, String message) { /* Local AI is deaf. */ }
|
||||||
public void hear(LobbyPlayer player, String message) { /* Local AI is deaf. */ }
|
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,360 +1,365 @@
|
|||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import java.util.Collection;
|
import com.google.common.collect.Iterables;
|
||||||
import java.util.List;
|
import com.google.common.collect.Lists;
|
||||||
import java.util.Map;
|
import forge.card.ICardFace;
|
||||||
|
import forge.card.mana.ManaCost;
|
||||||
import com.google.common.collect.Iterables;
|
import forge.card.mana.ManaCostParser;
|
||||||
import com.google.common.collect.Lists;
|
import forge.game.GameEntity;
|
||||||
|
import forge.game.card.Card;
|
||||||
import forge.card.ICardFace;
|
import forge.game.card.CounterType;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.game.cost.Cost;
|
||||||
import forge.card.mana.ManaCostParser;
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
import forge.game.GameEntity;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.card.Card;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.player.Player;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.mana.ManaCostBeingPaid;
|
import forge.game.player.PlayerController.BinaryChoiceType;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.player.Player;
|
import forge.game.spellability.SpellAbilityCondition;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.util.MyRandom;
|
||||||
import forge.game.player.PlayerController.BinaryChoiceType;
|
|
||||||
import forge.game.spellability.AbilitySub;
|
import java.util.Collection;
|
||||||
import forge.game.spellability.SpellAbility;
|
import java.util.List;
|
||||||
import forge.game.spellability.SpellAbilityCondition;
|
import java.util.Map;
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
/**
|
||||||
/**
|
* Base class for API-specific AI logic
|
||||||
* Base class for API-specific AI logic
|
* <p>
|
||||||
* <p>
|
* The three main methods are canPlayAI(), chkAIDrawback and doTriggerAINoCost.
|
||||||
* The three main methods are canPlayAI(), chkAIDrawback and doTriggerAINoCost.
|
*/
|
||||||
*/
|
public abstract class SpellAbilityAi {
|
||||||
public abstract class SpellAbilityAi {
|
|
||||||
|
public final boolean canPlayAIWithSubs(final Player aiPlayer, final SpellAbility sa) {
|
||||||
public final boolean canPlayAIWithSubs(final Player aiPlayer, final SpellAbility sa) {
|
if (!canPlayAI(aiPlayer, sa)) {
|
||||||
if (!canPlayAI(aiPlayer, sa)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
final AbilitySub subAb = sa.getSubAbility();
|
||||||
final AbilitySub subAb = sa.getSubAbility();
|
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb);
|
||||||
return subAb == null || 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 boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
final Card source = sa.getHostCard();
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
|
if (sa.getRestrictions() != null && !sa.getRestrictions().canPlay(source, sa)) {
|
||||||
if (sa.getRestrictions() != null && !sa.getRestrictions().canPlay(source, sa)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
return canPlayWithoutRestrict(ai, sa);
|
||||||
return canPlayWithoutRestrict(ai, sa);
|
}
|
||||||
}
|
|
||||||
|
protected boolean canPlayWithoutRestrict(final Player ai, final SpellAbility sa) {
|
||||||
protected boolean 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 (!checkConditions(ai, sa, sa.getConditions())) {
|
||||||
if (!checkConditions(ai, sa, sa.getConditions())) {
|
SpellAbility sub = sa.getSubAbility();
|
||||||
SpellAbility sub = sa.getSubAbility();
|
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {
|
||||||
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (sa.hasParam("AILogic")) {
|
||||||
if (sa.hasParam("AILogic")) {
|
final String logic = sa.getParam("AILogic");
|
||||||
final String logic = sa.getParam("AILogic");
|
if (!checkAiLogic(ai, sa, logic)) {
|
||||||
if (!checkAiLogic(ai, sa, logic)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler(), logic)) {
|
||||||
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler(), logic)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
||||||
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
}
|
if (cost != null && !willPayCosts(ai, sa, cost, source)) {
|
||||||
if (cost != null && !willPayCosts(ai, sa, cost, source)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
return checkApiLogic(ai, sa);
|
||||||
return checkApiLogic(ai, sa);
|
}
|
||||||
}
|
|
||||||
|
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) {
|
||||||
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) {
|
// 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();
|
||||||
con = (SpellAbilityCondition) con.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()) {
|
// need to use ManaCostBeingPaid check, can't use Cost#canPay
|
||||||
// need to use ManaCostBeingPaid check, can't use Cost#canPay
|
ManaCostBeingPaid paid = new ManaCostBeingPaid(new ManaCost(new ManaCostParser(con.getManaSpent())));
|
||||||
ManaCostBeingPaid paid = new ManaCostBeingPaid(new ManaCost(new ManaCostParser(con.getManaSpent())));
|
if (ComputerUtilMana.canPayManaCost(paid, sa, ai)) {
|
||||||
if (ComputerUtilMana.canPayManaCost(paid, sa, ai)) {
|
con.setManaSpent("");
|
||||||
con.setManaSpent("");
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return con.areMet(sa);
|
||||||
return con.areMet(sa);
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Checks if the AI will play a SpellAbility with the specified AiLogic
|
||||||
* Checks if the AI will play a SpellAbility with the specified AiLogic
|
*/
|
||||||
*/
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
if (aiLogic.equals("CheckCondition")) {
|
||||||
return !("Never".equals(aiLogic));
|
SpellAbility saCopy = sa.copy();
|
||||||
}
|
saCopy.setActivatingPlayer(ai);
|
||||||
|
return saCopy.getConditions().areMet(saCopy);
|
||||||
/**
|
}
|
||||||
* Checks if the AI is willing to pay for additional costs
|
|
||||||
* <p>
|
return !("Never".equals(aiLogic));
|
||||||
* 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)) {
|
* Checks if the AI is willing to pay for additional costs
|
||||||
return false;
|
* <p>
|
||||||
}
|
* Evaluated costs are: life, discard, sacrifice and counter-removal
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
|
*/
|
||||||
return false;
|
protected boolean willPayCosts(final Player ai, final SpellAbility sa, final Cost cost, final Card source) {
|
||||||
}
|
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa)) {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
|
||||||
/**
|
return false;
|
||||||
* Checks if the AI will play a SpellAbility based on its phase restrictions
|
}
|
||||||
*/
|
return true;
|
||||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
}
|
||||||
return true;
|
|
||||||
}
|
/**
|
||||||
|
* Checks if the AI will play a SpellAbility based on its phase restrictions
|
||||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph,
|
*/
|
||||||
final String logic) {
|
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||||
return checkPhaseRestrictions(ai, sa, ph);
|
return true;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* The rest of the logic not covered by the canPlayAI template is defined here
|
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph,
|
||||||
*/
|
final String logic) {
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
return checkPhaseRestrictions(ai, sa, ph);
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
}
|
||||||
return false; // prevent infinite loop
|
/**
|
||||||
}
|
* The rest of the logic not covered by the canPlayAI template is defined here
|
||||||
return MyRandom.getRandom().nextFloat() < .8f; // random success
|
*/
|
||||||
}
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
return false; // prevent infinite loop
|
||||||
if (!ComputerUtilCost.canPayCost(sa, aiPlayer) && !mandatory) {
|
}
|
||||||
return false;
|
return MyRandom.getRandom().nextFloat() < .8f; // random success
|
||||||
}
|
}
|
||||||
|
|
||||||
// a mandatory SpellAbility with targeting but without candidates,
|
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
// does not need to go any deeper
|
if (!ComputerUtilCost.canPayCost(sa, aiPlayer) && !mandatory) {
|
||||||
if (sa.usesTargeting() && mandatory && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
// a mandatory SpellAbility with targeting but without candidates,
|
||||||
return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory);
|
// does not need to go any deeper
|
||||||
}
|
if (sa.usesTargeting() && mandatory && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||||
|
return false;
|
||||||
public final boolean doTriggerNoCostWithSubs(final Player aiPlayer, final SpellAbility sa, final boolean mandatory)
|
}
|
||||||
{
|
|
||||||
if (!doTriggerAINoCost(aiPlayer, sa, mandatory)) {
|
return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory);
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
final AbilitySub subAb = sa.getSubAbility();
|
public final boolean doTriggerNoCostWithSubs(final Player aiPlayer, final SpellAbility sa, final boolean mandatory)
|
||||||
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb) || mandatory;
|
{
|
||||||
}
|
if (!doTriggerAINoCost(aiPlayer, sa, mandatory)) {
|
||||||
|
return false;
|
||||||
/**
|
}
|
||||||
* Handles the AI decision to play a triggered SpellAbility
|
final AbilitySub subAb = sa.getSubAbility();
|
||||||
*/
|
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb) || mandatory;
|
||||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
}
|
||||||
if (canPlayWithoutRestrict(aiPlayer, sa)) {
|
|
||||||
return true;
|
/**
|
||||||
}
|
* Handles the AI decision to play a triggered SpellAbility
|
||||||
|
*/
|
||||||
// not mandatory, short way out
|
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
if (!mandatory) {
|
if (canPlayWithoutRestrict(aiPlayer, sa)) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// invalid target might prevent it
|
// not mandatory, short way out
|
||||||
if (sa.usesTargeting()) {
|
if (!mandatory) {
|
||||||
// make list of players it does try to target
|
return false;
|
||||||
List<Player> players = Lists.newArrayList();
|
}
|
||||||
players.addAll(aiPlayer.getOpponents());
|
|
||||||
players.addAll(aiPlayer.getAllies());
|
// invalid target might prevent it
|
||||||
players.add(aiPlayer);
|
if (sa.usesTargeting()) {
|
||||||
|
// make list of players it does try to target
|
||||||
// try to target opponent, then ally, then itself
|
List<Player> players = Lists.newArrayList();
|
||||||
for (final Player p : players) {
|
players.addAll(aiPlayer.getOpponents());
|
||||||
if (p.canBeTargetedBy(sa) && sa.canTarget(p)) {
|
players.addAll(aiPlayer.getAllies());
|
||||||
sa.resetTargets();
|
players.add(aiPlayer);
|
||||||
sa.getTargets().add(p);
|
|
||||||
return true;
|
// try to target opponent, then ally, then itself
|
||||||
}
|
for (final Player p : players) {
|
||||||
}
|
if (p.canBeTargetedBy(sa) && sa.canTarget(p)) {
|
||||||
|
sa.resetTargets();
|
||||||
return false;
|
sa.getTargets().add(p);
|
||||||
}
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return false;
|
||||||
* Handles the AI decision to play a sub-SpellAbility
|
}
|
||||||
*/
|
return true;
|
||||||
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
}
|
||||||
// sub-SpellAbility might use targets too
|
|
||||||
if (sa.usesTargeting()) {
|
/**
|
||||||
// no Candidates, no adding to Stack
|
* Handles the AI decision to play a sub-SpellAbility
|
||||||
if (!sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
*/
|
||||||
return false;
|
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
||||||
}
|
// sub-SpellAbility might use targets too
|
||||||
// but if it does, it should override this function
|
if (sa.usesTargeting()) {
|
||||||
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");
|
// no Candidates, no adding to Stack
|
||||||
return false;
|
if (!sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||||
}
|
return false;
|
||||||
return true;
|
}
|
||||||
}
|
// 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;
|
||||||
* <p>
|
}
|
||||||
* isSorcerySpeed.
|
return true;
|
||||||
* </p>
|
}
|
||||||
*
|
|
||||||
* @param sa
|
/**
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* <p>
|
||||||
* @return a boolean.
|
* isSorcerySpeed.
|
||||||
*/
|
* </p>
|
||||||
protected static boolean isSorcerySpeed(final SpellAbility sa) {
|
*
|
||||||
return (sa.isSpell() && sa.getHostCard().isSorcery())
|
* @param sa
|
||||||
|| (sa.isAbility() && sa.getRestrictions().isSorcerySpeed())
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
|| (sa.getRestrictions().isPwAbility() && !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed."));
|
* @return a boolean.
|
||||||
}
|
*/
|
||||||
|
protected static boolean isSorcerySpeed(final SpellAbility sa) {
|
||||||
/**
|
return (sa.isSpell() && sa.getHostCard().isSorcery())
|
||||||
* <p>
|
|| (sa.isAbility() && sa.getRestrictions().isSorcerySpeed())
|
||||||
* playReusable.
|
|| (sa.getRestrictions().isPwAbility() && !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed."));
|
||||||
* </p>
|
}
|
||||||
*
|
|
||||||
* @param sa
|
/**
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* <p>
|
||||||
* @return a boolean.
|
* playReusable.
|
||||||
*/
|
* </p>
|
||||||
protected static boolean playReusable(final Player ai, final SpellAbility sa) {
|
*
|
||||||
PhaseHandler phase = ai.getGame().getPhaseHandler();
|
* @param sa
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
// TODO probably also consider if winter orb or similar are out
|
* @return a boolean.
|
||||||
|
*/
|
||||||
if (sa.getPayCosts() == null || sa instanceof AbilitySub) {
|
protected static boolean playReusable(final Player ai, final SpellAbility sa) {
|
||||||
return true; // This is only true for Drawbacks and triggers
|
PhaseHandler phase = ai.getGame().getPhaseHandler();
|
||||||
}
|
|
||||||
|
// TODO probably also consider if winter orb or similar are out
|
||||||
if (!sa.getPayCosts().isReusuableResource()) {
|
|
||||||
return false;
|
if (sa.getPayCosts() == null || sa instanceof AbilitySub) {
|
||||||
}
|
return true; // This is only true for Drawbacks and triggers
|
||||||
|
}
|
||||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
|
||||||
return true;
|
if (!sa.getPayCosts().isReusuableResource()) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
if (sa.getRestrictions().isPwAbility() && phase.is(PhaseType.MAIN2)) {
|
|
||||||
return true;
|
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||||
}
|
return true;
|
||||||
if (sa.isSpell() && !sa.isBuyBackAbility()) {
|
}
|
||||||
return false;
|
|
||||||
}
|
if (sa.getRestrictions().isPwAbility() && phase.is(PhaseType.MAIN2)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
|
if (sa.isSpell() && !sa.isBuyBackAbility()) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
/**
|
|
||||||
* TODO: Write javadoc for this method.
|
|
||||||
* @param aiPlayer
|
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
|
||||||
* @param ab
|
}
|
||||||
* @return
|
|
||||||
*/
|
/**
|
||||||
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
|
* TODO: Write javadoc for this method.
|
||||||
final AbilitySub subAb = ab.getSubAbility();
|
* @param aiPlayer
|
||||||
return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
|
* @param ab
|
||||||
}
|
* @return
|
||||||
|
*/
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
|
||||||
System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
final AbilitySub subAb = ab.getSubAbility();
|
||||||
return true;
|
return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
|
System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||||
boolean hasPlayer = false;
|
return true;
|
||||||
boolean hasCard = false;
|
}
|
||||||
boolean hasPlaneswalker = false;
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
for (T ent : options) {
|
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
|
||||||
if (ent instanceof Player) {
|
boolean hasPlayer = false;
|
||||||
hasPlayer = true;
|
boolean hasCard = false;
|
||||||
} else if (ent instanceof Card) {
|
boolean hasPlaneswalker = false;
|
||||||
hasCard = true;
|
|
||||||
if (((Card)ent).isPlaneswalker()) {
|
for (T ent : options) {
|
||||||
hasPlaneswalker = true;
|
if (ent instanceof Player) {
|
||||||
}
|
hasPlayer = true;
|
||||||
}
|
} else if (ent instanceof Card) {
|
||||||
}
|
hasCard = true;
|
||||||
|
if (((Card)ent).isPlaneswalker()) {
|
||||||
if (hasPlayer && hasPlaneswalker) {
|
hasPlaneswalker = true;
|
||||||
return (T) chooseSinglePlayerOrPlaneswalker(ai, sa, (Collection<GameEntity>) options);
|
}
|
||||||
} else if (hasCard) {
|
}
|
||||||
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer);
|
}
|
||||||
} else if (hasPlayer) {
|
|
||||||
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options);
|
if (hasPlayer && hasPlaneswalker) {
|
||||||
}
|
return (T) chooseSinglePlayerOrPlaneswalker(ai, sa, (Collection<GameEntity>) options);
|
||||||
|
} else if (hasCard) {
|
||||||
return null;
|
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer);
|
||||||
}
|
} else if (hasPlayer) {
|
||||||
|
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options);
|
||||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
}
|
||||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleSpellAbility is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
|
||||||
return spells.get(0);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells, Map<String, Object> params) {
|
||||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleCard is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleSpellAbility is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||||
return Iterables.getFirst(options, null);
|
return spells.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayer is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleCard is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||||
return Iterables.getFirst(options, null);
|
return Iterables.getFirst(options, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options) {
|
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayerOrPlaneswalker is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayer is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||||
return Iterables.getFirst(options, null);
|
return Iterables.getFirst(options, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) {
|
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options) {
|
||||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseCardName is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayerOrPlaneswalker is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||||
|
return Iterables.getFirst(options, null);
|
||||||
final ICardFace face = Iterables.getFirst(faces, null);
|
}
|
||||||
return face == null ? "" : face.getName();
|
|
||||||
}
|
public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) {
|
||||||
|
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseCardName is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
|
||||||
return max;
|
final ICardFace face = Iterables.getFirst(faces, null);
|
||||||
}
|
return face == null ? "" : face.getName();
|
||||||
|
}
|
||||||
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
|
||||||
return Iterables.getFirst(options, null);
|
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||||
}
|
return max;
|
||||||
|
}
|
||||||
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
|
|
||||||
return MyRandom.getRandom().nextBoolean();
|
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
||||||
}
|
return Iterables.getFirst(options, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
|
||||||
|
return MyRandom.getRandom().nextBoolean();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,172 +1,181 @@
|
|||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import java.util.Map;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import forge.ai.ability.*;
|
||||||
import com.google.common.collect.Maps;
|
import forge.game.ability.ApiType;
|
||||||
|
import forge.util.ReflectionUtil;
|
||||||
import forge.ai.ability.*;
|
|
||||||
import forge.game.ability.ApiType;
|
import java.util.Map;
|
||||||
import forge.util.ReflectionUtil;
|
|
||||||
|
public enum SpellApiToAi {
|
||||||
public enum SpellApiToAi {
|
Converter;
|
||||||
Converter;
|
|
||||||
|
private final Map<ApiType, SpellAbilityAi> apiToInstance = Maps.newEnumMap(ApiType.class);
|
||||||
private final Map<ApiType, SpellAbilityAi> apiToInstance = Maps.newEnumMap(ApiType.class);
|
|
||||||
|
// Do the extra copy to make an actual EnumMap (faster)
|
||||||
// Do the extra copy to make an actual EnumMap (faster)
|
private final Map<ApiType, Class<? extends SpellAbilityAi>> apiToClass = Maps.newEnumMap(ImmutableMap
|
||||||
private final Map<ApiType, Class<? extends SpellAbilityAi>> apiToClass = Maps.newEnumMap(ImmutableMap
|
.<ApiType, Class<? extends SpellAbilityAi>>builder()
|
||||||
.<ApiType, Class<? extends SpellAbilityAi>>builder()
|
.put(ApiType.Abandon, AlwaysPlayAi.class)
|
||||||
.put(ApiType.Abandon, AlwaysPlayAi.class)
|
.put(ApiType.ActivateAbility, ActivateAbilityAi.class)
|
||||||
.put(ApiType.ActivateAbility, ActivateAbilityAi.class)
|
.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class)
|
||||||
.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class)
|
.put(ApiType.AddPhase, AddPhaseAi.class)
|
||||||
.put(ApiType.AddPhase, AddPhaseAi.class)
|
.put(ApiType.AddTurn, AddTurnAi.class)
|
||||||
.put(ApiType.AddTurn, AddTurnAi.class)
|
.put(ApiType.Animate, AnimateAi.class)
|
||||||
.put(ApiType.Animate, AnimateAi.class)
|
.put(ApiType.AnimateAll, AnimateAllAi.class)
|
||||||
.put(ApiType.AnimateAll, AnimateAllAi.class)
|
.put(ApiType.Attach, AttachAi.class)
|
||||||
.put(ApiType.Attach, AttachAi.class)
|
.put(ApiType.Ascend, AlwaysPlayAi.class)
|
||||||
.put(ApiType.Balance, BalanceAi.class)
|
.put(ApiType.AssignGroup, AssignGroupAi.class)
|
||||||
.put(ApiType.BecomeMonarch, AlwaysPlayAi.class)
|
.put(ApiType.Balance, BalanceAi.class)
|
||||||
.put(ApiType.BecomesBlocked, BecomesBlockedAi.class)
|
.put(ApiType.BecomeMonarch, AlwaysPlayAi.class)
|
||||||
.put(ApiType.BidLife, BidLifeAi.class)
|
.put(ApiType.BecomesBlocked, BecomesBlockedAi.class)
|
||||||
.put(ApiType.Bond, BondAi.class)
|
.put(ApiType.BidLife, BidLifeAi.class)
|
||||||
.put(ApiType.Branch, AlwaysPlayAi.class)
|
.put(ApiType.Bond, BondAi.class)
|
||||||
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
.put(ApiType.Branch, AlwaysPlayAi.class)
|
||||||
.put(ApiType.ChangeZone, ChangeZoneAi.class)
|
.put(ApiType.ChangeCombatants, CannotPlayAi.class)
|
||||||
.put(ApiType.ChangeZoneAll, ChangeZoneAllAi.class)
|
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
||||||
.put(ApiType.Charm, CharmAi.class)
|
.put(ApiType.ChangeZone, ChangeZoneAi.class)
|
||||||
.put(ApiType.ChooseCard, ChooseCardAi.class)
|
.put(ApiType.ChangeZoneAll, ChangeZoneAllAi.class)
|
||||||
.put(ApiType.ChooseColor, ChooseColorAi.class)
|
.put(ApiType.Charm, CharmAi.class)
|
||||||
.put(ApiType.ChooseDirection, ChooseDirectionAi.class)
|
.put(ApiType.ChooseCard, ChooseCardAi.class)
|
||||||
.put(ApiType.ChooseNumber, ChooseNumberAi.class)
|
.put(ApiType.ChooseColor, ChooseColorAi.class)
|
||||||
.put(ApiType.ChoosePlayer, ChoosePlayerAi.class)
|
.put(ApiType.ChooseDirection, ChooseDirectionAi.class)
|
||||||
.put(ApiType.ChooseSource, ChooseSourceAi.class)
|
.put(ApiType.ChooseNumber, ChooseNumberAi.class)
|
||||||
.put(ApiType.ChooseType, ChooseTypeAi.class)
|
.put(ApiType.ChoosePlayer, ChoosePlayerAi.class)
|
||||||
.put(ApiType.Clash, ClashAi.class)
|
.put(ApiType.ChooseSource, ChooseSourceAi.class)
|
||||||
.put(ApiType.Cleanup, AlwaysPlayAi.class)
|
.put(ApiType.ChooseType, ChooseTypeAi.class)
|
||||||
.put(ApiType.Clone, CloneAi.class)
|
.put(ApiType.Clash, ClashAi.class)
|
||||||
.put(ApiType.CopyPermanent, CopyPermanentAi.class)
|
.put(ApiType.Cleanup, AlwaysPlayAi.class)
|
||||||
.put(ApiType.CopySpellAbility, CopySpellAbilityAi.class)
|
.put(ApiType.Clone, CloneAi.class)
|
||||||
.put(ApiType.ControlPlayer, CannotPlayAi.class)
|
.put(ApiType.CopyPermanent, CopyPermanentAi.class)
|
||||||
.put(ApiType.ControlSpell, CannotPlayAi.class)
|
.put(ApiType.CopySpellAbility, CopySpellAbilityAi.class)
|
||||||
.put(ApiType.Counter, CounterAi.class)
|
.put(ApiType.ControlPlayer, CannotPlayAi.class)
|
||||||
.put(ApiType.DamageAll, DamageAllAi.class)
|
.put(ApiType.ControlSpell, CannotPlayAi.class)
|
||||||
.put(ApiType.DealDamage, DamageDealAi.class)
|
.put(ApiType.Counter, CounterAi.class)
|
||||||
.put(ApiType.Debuff, DebuffAi.class)
|
.put(ApiType.DamageAll, DamageAllAi.class)
|
||||||
.put(ApiType.DeclareCombatants, CannotPlayAi.class)
|
.put(ApiType.DealDamage, DamageDealAi.class)
|
||||||
.put(ApiType.DelayedTrigger, DelayedTriggerAi.class)
|
.put(ApiType.Debuff, DebuffAi.class)
|
||||||
.put(ApiType.Destroy, DestroyAi.class)
|
.put(ApiType.DeclareCombatants, CannotPlayAi.class)
|
||||||
.put(ApiType.DestroyAll, DestroyAllAi.class)
|
.put(ApiType.DelayedTrigger, DelayedTriggerAi.class)
|
||||||
.put(ApiType.Dig, DigAi.class)
|
.put(ApiType.Destroy, DestroyAi.class)
|
||||||
.put(ApiType.DigUntil, DigUntilAi.class)
|
.put(ApiType.DestroyAll, DestroyAllAi.class)
|
||||||
.put(ApiType.Discard, DiscardAi.class)
|
.put(ApiType.Dig, DigAi.class)
|
||||||
.put(ApiType.DrainMana, DrainManaAi.class)
|
.put(ApiType.DigUntil, DigUntilAi.class)
|
||||||
.put(ApiType.Draw, DrawAi.class)
|
.put(ApiType.Discard, DiscardAi.class)
|
||||||
.put(ApiType.EachDamage, DamageEachAi.class)
|
.put(ApiType.DrainMana, DrainManaAi.class)
|
||||||
.put(ApiType.Effect, EffectAi.class)
|
.put(ApiType.Draw, DrawAi.class)
|
||||||
.put(ApiType.Encode, EncodeAi.class)
|
.put(ApiType.EachDamage, DamageEachAi.class)
|
||||||
.put(ApiType.EndTurn, EndTurnAi.class)
|
.put(ApiType.Effect, EffectAi.class)
|
||||||
.put(ApiType.ExchangeLife, LifeExchangeAi.class)
|
.put(ApiType.Encode, EncodeAi.class)
|
||||||
.put(ApiType.ExchangeControl, ControlExchangeAi.class)
|
.put(ApiType.EndTurn, EndTurnAi.class)
|
||||||
.put(ApiType.ExchangeControlVariant, CannotPlayAi.class)
|
.put(ApiType.ExchangeLife, LifeExchangeAi.class)
|
||||||
.put(ApiType.ExchangePower, PowerExchangeAi.class)
|
.put(ApiType.ExchangeLifeVariant, LifeExchangeVariantAi.class)
|
||||||
.put(ApiType.ExchangeZone, ZoneExchangeAi.class)
|
.put(ApiType.ExchangeControl, ControlExchangeAi.class)
|
||||||
.put(ApiType.Fight, FightAi.class)
|
.put(ApiType.ExchangeControlVariant, CannotPlayAi.class)
|
||||||
.put(ApiType.FlipACoin, FlipACoinAi.class)
|
.put(ApiType.ExchangePower, PowerExchangeAi.class)
|
||||||
.put(ApiType.Fog, FogAi.class)
|
.put(ApiType.ExchangeZone, ZoneExchangeAi.class)
|
||||||
.put(ApiType.GainControl, ControlGainAi.class)
|
.put(ApiType.Explore, ExploreAi.class)
|
||||||
.put(ApiType.GainLife, LifeGainAi.class)
|
.put(ApiType.Fight, FightAi.class)
|
||||||
.put(ApiType.GainOwnership, CannotPlayAi.class)
|
.put(ApiType.FlipACoin, FlipACoinAi.class)
|
||||||
.put(ApiType.GenericChoice, ChooseGenericEffectAi.class)
|
.put(ApiType.Fog, FogAi.class)
|
||||||
.put(ApiType.Goad, GoadAi.class)
|
.put(ApiType.GainControl, ControlGainAi.class)
|
||||||
.put(ApiType.Haunt, HauntAi.class)
|
.put(ApiType.GainLife, LifeGainAi.class)
|
||||||
.put(ApiType.LoseLife, LifeLoseAi.class)
|
.put(ApiType.GainOwnership, CannotPlayAi.class)
|
||||||
.put(ApiType.LosesGame, GameLossAi.class)
|
.put(ApiType.GameDrawn, CannotPlayAi.class)
|
||||||
.put(ApiType.Mana, ManaEffectAi.class)
|
.put(ApiType.GenericChoice, ChooseGenericEffectAi.class)
|
||||||
.put(ApiType.ManaReflected, CannotPlayAi.class)
|
.put(ApiType.Goad, GoadAi.class)
|
||||||
.put(ApiType.Manifest, ManifestAi.class)
|
.put(ApiType.Haunt, HauntAi.class)
|
||||||
.put(ApiType.Meld, MeldAi.class)
|
.put(ApiType.LoseLife, LifeLoseAi.class)
|
||||||
.put(ApiType.Mill, MillAi.class)
|
.put(ApiType.LosesGame, GameLossAi.class)
|
||||||
.put(ApiType.MoveCounter, CountersMoveAi.class)
|
.put(ApiType.Mana, ManaEffectAi.class)
|
||||||
.put(ApiType.MultiplePiles, CannotPlayAi.class)
|
.put(ApiType.ManaReflected, CannotPlayAi.class)
|
||||||
.put(ApiType.MultiplyCounter, CountersMultiplyAi.class)
|
.put(ApiType.Manifest, ManifestAi.class)
|
||||||
.put(ApiType.MustAttack, MustAttackAi.class)
|
.put(ApiType.Meld, MeldAi.class)
|
||||||
.put(ApiType.MustBlock, MustBlockAi.class)
|
.put(ApiType.Mill, MillAi.class)
|
||||||
.put(ApiType.NameCard, ChooseCardNameAi.class)
|
.put(ApiType.MoveCounter, CountersMoveAi.class)
|
||||||
.put(ApiType.NoteCounters, AlwaysPlayAi.class)
|
.put(ApiType.MultiplePiles, CannotPlayAi.class)
|
||||||
.put(ApiType.PeekAndReveal, PeekAndRevealAi.class)
|
.put(ApiType.MultiplyCounter, CountersMultiplyAi.class)
|
||||||
.put(ApiType.PermanentCreature, PermanentCreatureAi.class)
|
.put(ApiType.MustAttack, MustAttackAi.class)
|
||||||
.put(ApiType.PermanentNoncreature, PermanentNoncreatureAi.class)
|
.put(ApiType.MustBlock, MustBlockAi.class)
|
||||||
.put(ApiType.Phases, PhasesAi.class)
|
.put(ApiType.NameCard, ChooseCardNameAi.class)
|
||||||
.put(ApiType.Planeswalk, AlwaysPlayAi.class)
|
.put(ApiType.NoteCounters, AlwaysPlayAi.class)
|
||||||
.put(ApiType.Play, PlayAi.class)
|
.put(ApiType.PeekAndReveal, PeekAndRevealAi.class)
|
||||||
.put(ApiType.PlayLandVariant, CannotPlayAi.class)
|
.put(ApiType.PermanentCreature, PermanentCreatureAi.class)
|
||||||
.put(ApiType.Poison, PoisonAi.class)
|
.put(ApiType.PermanentNoncreature, PermanentNoncreatureAi.class)
|
||||||
.put(ApiType.PreventDamage, DamagePreventAi.class)
|
.put(ApiType.Phases, PhasesAi.class)
|
||||||
.put(ApiType.PreventDamageAll, DamagePreventAllAi.class)
|
.put(ApiType.Planeswalk, AlwaysPlayAi.class)
|
||||||
.put(ApiType.Proliferate, CountersProliferateAi.class)
|
.put(ApiType.Play, PlayAi.class)
|
||||||
.put(ApiType.Protection, ProtectAi.class)
|
.put(ApiType.PlayLandVariant, CannotPlayAi.class)
|
||||||
.put(ApiType.ProtectionAll, ProtectAllAi.class)
|
.put(ApiType.Poison, PoisonAi.class)
|
||||||
.put(ApiType.Pump, PumpAi.class)
|
.put(ApiType.PreventDamage, DamagePreventAi.class)
|
||||||
.put(ApiType.PumpAll, PumpAllAi.class)
|
.put(ApiType.PreventDamageAll, DamagePreventAllAi.class)
|
||||||
.put(ApiType.PutCounter, CountersPutAi.class)
|
.put(ApiType.Proliferate, CountersProliferateAi.class)
|
||||||
.put(ApiType.PutCounterAll, CountersPutAllAi.class)
|
.put(ApiType.Protection, ProtectAi.class)
|
||||||
.put(ApiType.RearrangeTopOfLibrary, RearrangeTopOfLibraryAi.class)
|
.put(ApiType.ProtectionAll, ProtectAllAi.class)
|
||||||
.put(ApiType.Regenerate, RegenerateAi.class)
|
.put(ApiType.Pump, PumpAi.class)
|
||||||
.put(ApiType.RegenerateAll, RegenerateAllAi.class)
|
.put(ApiType.PumpAll, PumpAllAi.class)
|
||||||
.put(ApiType.RemoveCounter, CountersRemoveAi.class)
|
.put(ApiType.PutCounter, CountersPutAi.class)
|
||||||
.put(ApiType.RemoveCounterAll, CannotPlayAi.class)
|
.put(ApiType.PutCounterAll, CountersPutAllAi.class)
|
||||||
.put(ApiType.RemoveFromCombat, RemoveFromCombatAi.class)
|
.put(ApiType.RearrangeTopOfLibrary, RearrangeTopOfLibraryAi.class)
|
||||||
.put(ApiType.ReorderZone, AlwaysPlayAi.class)
|
.put(ApiType.Regenerate, RegenerateAi.class)
|
||||||
.put(ApiType.Repeat, RepeatAi.class)
|
.put(ApiType.RegenerateAll, RegenerateAllAi.class)
|
||||||
.put(ApiType.RepeatEach, RepeatEachAi.class)
|
.put(ApiType.Regeneration, AlwaysPlayAi.class)
|
||||||
.put(ApiType.ReplaceEffect, AlwaysPlayAi.class)
|
.put(ApiType.RemoveCounter, CountersRemoveAi.class)
|
||||||
.put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class)
|
.put(ApiType.RemoveCounterAll, CannotPlayAi.class)
|
||||||
.put(ApiType.RestartGame, RestartGameAi.class)
|
.put(ApiType.RemoveFromCombat, RemoveFromCombatAi.class)
|
||||||
.put(ApiType.Reveal, RevealAi.class)
|
.put(ApiType.ReorderZone, AlwaysPlayAi.class)
|
||||||
.put(ApiType.RevealHand, RevealHandAi.class)
|
.put(ApiType.Repeat, RepeatAi.class)
|
||||||
.put(ApiType.ReverseTurnOrder, AlwaysPlayAi.class)
|
.put(ApiType.RepeatEach, RepeatEachAi.class)
|
||||||
.put(ApiType.RollPlanarDice, RollPlanarDiceAi.class)
|
.put(ApiType.ReplaceEffect, AlwaysPlayAi.class)
|
||||||
.put(ApiType.RunSVarAbility, AlwaysPlayAi.class)
|
.put(ApiType.ReplaceDamage, AlwaysPlayAi.class)
|
||||||
.put(ApiType.Sacrifice, SacrificeAi.class)
|
.put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class)
|
||||||
.put(ApiType.SacrificeAll, SacrificeAllAi.class)
|
.put(ApiType.RestartGame, RestartGameAi.class)
|
||||||
.put(ApiType.Scry, ScryAi.class)
|
.put(ApiType.Reveal, RevealAi.class)
|
||||||
.put(ApiType.SetInMotion, AlwaysPlayAi.class)
|
.put(ApiType.RevealHand, RevealHandAi.class)
|
||||||
.put(ApiType.SetLife, LifeSetAi.class)
|
.put(ApiType.ReverseTurnOrder, AlwaysPlayAi.class)
|
||||||
.put(ApiType.SetState, SetStateAi.class)
|
.put(ApiType.RollPlanarDice, RollPlanarDiceAi.class)
|
||||||
.put(ApiType.Shuffle, ShuffleAi.class)
|
.put(ApiType.RunSVarAbility, AlwaysPlayAi.class)
|
||||||
.put(ApiType.SkipTurn, SkipTurnAi.class)
|
.put(ApiType.Sacrifice, SacrificeAi.class)
|
||||||
.put(ApiType.StoreMap, StoreMapAi.class)
|
.put(ApiType.SacrificeAll, SacrificeAllAi.class)
|
||||||
.put(ApiType.StoreSVar, StoreSVarAi.class)
|
.put(ApiType.Scry, ScryAi.class)
|
||||||
.put(ApiType.Tap, TapAi.class)
|
.put(ApiType.SetInMotion, AlwaysPlayAi.class)
|
||||||
.put(ApiType.TapAll, TapAllAi.class)
|
.put(ApiType.SetLife, LifeSetAi.class)
|
||||||
.put(ApiType.TapOrUntap, TapOrUntapAi.class)
|
.put(ApiType.SetState, SetStateAi.class)
|
||||||
.put(ApiType.TapOrUntapAll, TapOrUntapAllAi.class)
|
.put(ApiType.Shuffle, ShuffleAi.class)
|
||||||
.put(ApiType.Token, TokenAi.class)
|
.put(ApiType.SkipTurn, SkipTurnAi.class)
|
||||||
.put(ApiType.TwoPiles, TwoPilesAi.class)
|
.put(ApiType.StoreMap, StoreMapAi.class)
|
||||||
.put(ApiType.Unattach, CannotPlayAi.class)
|
.put(ApiType.StoreSVar, StoreSVarAi.class)
|
||||||
.put(ApiType.UnattachAll, UnattachAllAi.class)
|
.put(ApiType.Surveil, SurveilAi.class)
|
||||||
.put(ApiType.Untap, UntapAi.class)
|
.put(ApiType.Tap, TapAi.class)
|
||||||
.put(ApiType.UntapAll, UntapAllAi.class)
|
.put(ApiType.TapAll, TapAllAi.class)
|
||||||
.put(ApiType.Vote, VoteAi.class)
|
.put(ApiType.TapOrUntap, TapOrUntapAi.class)
|
||||||
.put(ApiType.WinsGame, GameWinAi.class)
|
.put(ApiType.TapOrUntapAll, TapOrUntapAllAi.class)
|
||||||
|
.put(ApiType.Token, TokenAi.class)
|
||||||
.put(ApiType.InternalEtbReplacement, CanPlayAsDrawbackAi.class)
|
.put(ApiType.TwoPiles, TwoPilesAi.class)
|
||||||
.put(ApiType.InternalLegendaryRule, LegendaryRuleAi.class)
|
.put(ApiType.Unattach, CannotPlayAi.class)
|
||||||
.put(ApiType.InternalIgnoreEffect, CannotPlayAi.class)
|
.put(ApiType.UnattachAll, UnattachAllAi.class)
|
||||||
.build());
|
.put(ApiType.Untap, UntapAi.class)
|
||||||
|
.put(ApiType.UntapAll, UntapAllAi.class)
|
||||||
public SpellAbilityAi get(final ApiType api) {
|
.put(ApiType.Vote, VoteAi.class)
|
||||||
SpellAbilityAi result = apiToInstance.get(api);
|
.put(ApiType.WinsGame, GameWinAi.class)
|
||||||
if (null == result) {
|
|
||||||
Class<? extends SpellAbilityAi> clz = apiToClass.get(api);
|
.put(ApiType.DamageResolve, AlwaysPlayAi.class)
|
||||||
if (null == clz) {
|
.put(ApiType.InternalEtbReplacement, CanPlayAsDrawbackAi.class)
|
||||||
System.err.println("No AI assigned for API: " + api);
|
.put(ApiType.InternalLegendaryRule, LegendaryRuleAi.class)
|
||||||
clz = CannotPlayAi.class;
|
.put(ApiType.InternalIgnoreEffect, CannotPlayAi.class)
|
||||||
}
|
.build());
|
||||||
result = ReflectionUtil.makeDefaultInstanceOf(clz);
|
|
||||||
apiToInstance.put(api, result);
|
public SpellAbilityAi get(final ApiType api) {
|
||||||
}
|
SpellAbilityAi result = apiToInstance.get(api);
|
||||||
return result;
|
if (null == result) {
|
||||||
}
|
Class<? extends SpellAbilityAi> clz = apiToClass.get(api);
|
||||||
}
|
if (null == clz) {
|
||||||
|
System.err.println("No AI assigned for API: " + api);
|
||||||
|
clz = CannotPlayAi.class;
|
||||||
|
}
|
||||||
|
result = ReflectionUtil.makeDefaultInstanceOf(clz);
|
||||||
|
apiToInstance.put(api, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,100 +1,101 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.Card;
|
||||||
import forge.game.player.Player;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.util.MyRandom;
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.MyRandom;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
public class ActivateAbilityAi extends SpellAbilityAi {
|
|
||||||
|
public class ActivateAbilityAi extends SpellAbilityAi {
|
||||||
@Override
|
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
@Override
|
||||||
// AI cannot use this properly until he can use SAs during Humans turn
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
// AI cannot use this properly until he can use SAs during Humans turn
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
final Card source = sa.getHostCard();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Player opp = ai.getOpponent();
|
final Card source = sa.getHostCard();
|
||||||
final Random r = MyRandom.getRandom();
|
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
|
|
||||||
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
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 false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return randomReturn;
|
return randomReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Player opp = ai.getOpponent();
|
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
|
|
||||||
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 true;
|
||||||
} 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)) {
|
if (!defined.contains(opp)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
// AI cannot use this properly until he can use SAs during Humans turn
|
// AI cannot use this properly until he can use SAs during Humans turn
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
boolean randomReturn = true;
|
boolean randomReturn = true;
|
||||||
|
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
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 false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(ai.getOpponent());
|
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||||
}
|
}
|
||||||
|
|
||||||
return randomReturn;
|
return randomReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
|
||||||
return spells.get(0);
|
Map<String, Object> params) {
|
||||||
}
|
return spells.get(0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Write javadoc for this type.
|
* TODO: Write javadoc for this type.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class AddPhaseAi extends SpellAbilityAi {
|
public class AddPhaseAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
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.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
public class AlwaysPlayAi extends SpellAbilityAi {
|
|
||||||
/* (non-Javadoc)
|
public class AlwaysPlayAi extends SpellAbilityAi {
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
/* (non-Javadoc)
|
||||||
*/
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
@Override
|
*/
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
@Override
|
||||||
return true;
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,19 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
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 AnimateAllAi extends SpellAbilityAi {
|
public class AnimateAllAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
return false;
|
return false;
|
||||||
} // end animateAllCanPlayAI()
|
} // end animateAllCanPlayAI()
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
return false;
|
return mandatory;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // end class AbilityFactoryAnimate
|
} // end class AbilityFactoryAnimate
|
||||||
|
|||||||
33
forge-ai/src/main/java/forge/ai/ability/AssignGroupAi.java
Normal file
33
forge-ai/src/main/java/forge/ai/ability/AssignGroupAi.java
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
public class AssignGroupAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
// TODO: Currently this AI relies on the card-specific limiting hints (NeedsToPlay / NeedsToPlayVar),
|
||||||
|
// otherwise the AI considers the card playable.
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells, Map<String, Object> params) {
|
||||||
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
|
if (logic.equals("FriendOrFoe")) {
|
||||||
|
if (params.containsKey("Affected") && spells.size() >= 2) {
|
||||||
|
Player t = (Player) params.get("Affected");
|
||||||
|
return spells.get(player.isOpponentOf(t) ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Iterables.getFirst(spells, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,47 +1,48 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.player.Player;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.player.Player;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.MyRandom;
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.MyRandom;
|
||||||
public class BalanceAi extends SpellAbilityAi {
|
|
||||||
@Override
|
public class BalanceAi extends SpellAbilityAi {
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
@Override
|
||||||
String logic = sa.getParam("AILogic");
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
String logic = sa.getParam("AILogic");
|
||||||
int diff = 0;
|
|
||||||
// TODO Add support for multiplayer logic
|
int diff = 0;
|
||||||
final Player opp = aiPlayer.getOpponent();
|
// TODO Add support for multiplayer logic
|
||||||
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
final Player opp = ComputerUtil.getOpponentFor(aiPlayer);
|
||||||
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
||||||
|
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||||
if ("BalanceCreaturesAndLands".equals(logic)) {
|
|
||||||
// Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting
|
if ("BalanceCreaturesAndLands".equals(logic)) {
|
||||||
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
|
// Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting
|
||||||
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
|
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
|
||||||
diff += 1.5 * ( CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
|
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
|
||||||
CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size());
|
diff += 1.5 * ( CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
|
||||||
}
|
CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size());
|
||||||
else if ("BalancePermanents".equals(logic)) {
|
}
|
||||||
// Don't cast if you have to sacrifice permanents
|
else if ("BalancePermanents".equals(logic)) {
|
||||||
diff += humPerms.size() - compPerms.size();
|
// Don't cast if you have to sacrifice permanents
|
||||||
}
|
diff += humPerms.size() - compPerms.size();
|
||||||
|
}
|
||||||
if (diff < 0) {
|
|
||||||
// Don't sacrifice permanents even if opponent has a ton of cards in hand
|
if (diff < 0) {
|
||||||
return false;
|
// Don't sacrifice permanents even if opponent has a ton of cards in hand
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
final CardCollectionView humHand = opp.getCardsIn(ZoneType.Hand);
|
|
||||||
final CardCollectionView compHand = aiPlayer.getCardsIn(ZoneType.Hand);
|
final CardCollectionView humHand = opp.getCardsIn(ZoneType.Hand);
|
||||||
diff += 0.5 * (humHand.size() - compHand.size());
|
final CardCollectionView compHand = aiPlayer.getCardsIn(ZoneType.Hand);
|
||||||
|
diff += 0.5 * (humHand.size() - compHand.size());
|
||||||
// Larger differential == more chance to actually cast this spell
|
|
||||||
return diff > 2 && MyRandom.getRandom().nextInt(100) < diff*10;
|
// Larger differential == more chance to actually cast this spell
|
||||||
}
|
return diff > 2 && MyRandom.getRandom().nextInt(100) < diff*10;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,70 +1,71 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.player.Player;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
public class BecomesBlockedAi extends SpellAbilityAi {
|
|
||||||
@Override
|
public class BecomesBlockedAi extends SpellAbilityAi {
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
@Override
|
||||||
final Card source = sa.getHostCard();
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = aiPlayer.getGame();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
final Game game = aiPlayer.getGame();
|
||||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
|
||||||
|| !game.getPhaseHandler().getPlayerTurn().isOpponentOf(aiPlayer)) {
|
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
return false;
|
|| !game.getPhaseHandler().getPlayerTurn().isOpponentOf(aiPlayer)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
if (tgt != null) {
|
|
||||||
sa.resetTargets();
|
if (tgt != null) {
|
||||||
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), aiPlayer.getOpponents());
|
sa.resetTargets();
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), aiPlayer.getOpponents());
|
||||||
list = CardLists.getTargetableCards(list, sa);
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||||
list = CardLists.getNotKeyword(list, "Trample");
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
|
list = CardLists.getNotKeyword(list, Keyword.TRAMPLE);
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
|
||||||
Card choice = null;
|
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||||
|
Card choice = null;
|
||||||
if (list.isEmpty()) {
|
|
||||||
return false;
|
if (list.isEmpty()) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
|
||||||
|
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
if (choice == null) { // can't find anything left
|
|
||||||
return false;
|
if (choice == null) { // can't find anything left
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
list.remove(choice);
|
|
||||||
sa.getTargets().add(choice);
|
list.remove(choice);
|
||||||
}
|
sa.getTargets().add(choice);
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
@Override
|
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
@Override
|
||||||
// TODO - implement AI
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
return false;
|
// TODO - implement AI
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
@Override
|
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
@Override
|
||||||
boolean chance;
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
boolean chance;
|
||||||
// TODO - implement AI
|
|
||||||
chance = false;
|
// TODO - implement AI
|
||||||
|
chance = false;
|
||||||
return chance;
|
|
||||||
}
|
return chance;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,57 +1,58 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.game.Game;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.Game;
|
||||||
import forge.game.card.CardFactoryUtil;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardFactoryUtil;
|
||||||
import forge.game.player.Player;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.util.MyRandom;
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.MyRandom;
|
||||||
public class BidLifeAi extends SpellAbilityAi {
|
|
||||||
|
public class BidLifeAi extends SpellAbilityAi {
|
||||||
@Override
|
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
@Override
|
||||||
final Card source = sa.getHostCard();
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
final Game game = source.getGame();
|
final Card source = sa.getHostCard();
|
||||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
final Game game = source.getGame();
|
||||||
if (tgt != null) {
|
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
sa.resetTargets();
|
if (tgt != null) {
|
||||||
if (tgt.canTgtCreature()) {
|
sa.resetTargets();
|
||||||
List<Card> list = CardLists.getTargetableCards(aiPlayer.getOpponent().getCardsIn(ZoneType.Battlefield), sa);
|
if (tgt.canTgtCreature()) {
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
List<Card> list = CardLists.getTargetableCards(ComputerUtil.getOpponentFor(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
|
||||||
if (list.isEmpty()) {
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||||
return false;
|
if (list.isEmpty()) {
|
||||||
}
|
return false;
|
||||||
Card c = ComputerUtilCard.getBestCreatureAI(list);
|
}
|
||||||
if (sa.canTarget(c)) {
|
Card c = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
sa.getTargets().add(c);
|
if (sa.canTarget(c)) {
|
||||||
} else {
|
sa.getTargets().add(c);
|
||||||
return false;
|
} else {
|
||||||
}
|
return false;
|
||||||
} else if (tgt.getZone().contains(ZoneType.Stack)) {
|
}
|
||||||
if (game.getStack().isEmpty()) {
|
} else if (tgt.getZone().contains(ZoneType.Stack)) {
|
||||||
return false;
|
if (game.getStack().isEmpty()) {
|
||||||
}
|
return false;
|
||||||
final SpellAbility topSA = game.getStack().peekAbility();
|
}
|
||||||
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || aiPlayer.equals(topSA.getActivatingPlayer())) {
|
final SpellAbility topSA = game.getStack().peekAbility();
|
||||||
return false;
|
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || aiPlayer.equals(topSA.getActivatingPlayer())) {
|
||||||
}
|
return false;
|
||||||
if (sa.canTargetSpellAbility(topSA)) {
|
}
|
||||||
sa.getTargets().add(topSA);
|
if (sa.canTargetSpellAbility(topSA)) {
|
||||||
} else {
|
sa.getTargets().add(topSA);
|
||||||
return false;
|
} else {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
}
|
||||||
return chance;
|
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
}
|
return chance;
|
||||||
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,56 +1,61 @@
|
|||||||
/*
|
/*
|
||||||
* Forge: Play Magic: the Gathering.
|
* Forge: Play Magic: the Gathering.
|
||||||
* Copyright (C) 2011 Forge Team
|
* Copyright (C) 2011 Forge Team
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
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;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* AbilityFactoryBond class.
|
* AbilityFactoryBond class.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author Forge
|
* @author Forge
|
||||||
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
|
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
|
||||||
*/
|
*/
|
||||||
public final class BondAi extends SpellAbilityAi {
|
public final class BondAi extends SpellAbilityAi {
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* bondCanPlayAI.
|
* bondCanPlayAI.
|
||||||
* </p>
|
* </p>
|
||||||
* @param sa
|
* @param aiPlayer
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a {@link forge.game.player.Player} object.
|
||||||
* @param af
|
* @param sa
|
||||||
* a {@link forge.game.ability.AbilityFactory} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
*
|
*
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
return true;
|
return true;
|
||||||
} // end bondCanPlayAI()
|
} // end bondCanPlayAI()
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||||
return ComputerUtilCard.getBestCreatureAI(options);
|
return ComputerUtilCard.getBestCreatureAI(options);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,44 +1,46 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
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 java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
public class CanPlayAsDrawbackAi extends SpellAbilityAi {
|
|
||||||
|
public class CanPlayAsDrawbackAi extends SpellAbilityAi {
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
/* (non-Javadoc)
|
||||||
*/
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
@Override
|
*/
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
@Override
|
||||||
return false;
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
/**
|
|
||||||
* <p>
|
/**
|
||||||
* copySpellTriggerAI.
|
* <p>
|
||||||
* </p>
|
* copySpellTriggerAI.
|
||||||
* @param sa
|
* </p>
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* @param sa
|
||||||
* @param mandatory
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* a boolean.
|
* @param mandatory
|
||||||
* @param af
|
* a boolean.
|
||||||
* a {@link forge.game.ability.AbilityFactory} object.
|
* @param af
|
||||||
*
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
* @return a boolean.
|
*
|
||||||
*/
|
* @return a boolean.
|
||||||
@Override
|
*/
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
@Override
|
||||||
return false;
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
@Override
|
||||||
// This might be called from CopySpellAbilityEffect - to hide warning (for having no overload) use this simple overload
|
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
|
||||||
return spells.get(0);
|
Map<String, Object> params) {
|
||||||
}
|
// This might be called from CopySpellAbilityEffect - to hide warning (for having no overload) use this simple overload
|
||||||
}
|
return spells.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
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 CannotPlayAi extends SpellAbilityAi {
|
public class CannotPlayAi 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 aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (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 boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
return canPlayAI(aiPlayer, sa);
|
return canPlayAI(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,93 +1,93 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.ai.SpellAbilityAi;
|
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;
|
||||||
import forge.game.mana.ManaCostBeingPaid;
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
public class ChangeTargetsAi extends SpellAbilityAi {
|
public class ChangeTargetsAi extends SpellAbilityAi {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||||
* forge.game.spellability.SpellAbility)
|
* forge.game.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected boolean 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);
|
||||||
|
|
||||||
if ("Self".equals(sa.getParam("DefinedMagnet"))) {
|
if ("Self".equals(sa.getParam("DefinedMagnet"))) {
|
||||||
return doSpellMagnet(sa, topSa, ai);
|
return doSpellMagnet(sa, topSa, ai);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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();
|
return sa.isMandatory();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doSpellMagnet(SpellAbility sa, SpellAbility topSa, Player aiPlayer) {
|
private boolean 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 false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getTargets().getNumTargeted() != 0) {
|
if (sa.getTargets().getNumTargeted() != 0) {
|
||||||
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
|
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!topSa.usesTargeting() || topSa.getTargets().getTargetCards().contains(sa.getHostCard())) {
|
if (!topSa.usesTargeting() || topSa.getTargets().getTargetCards().contains(sa.getHostCard())) {
|
||||||
// if this does not target at all or already targets host, no need to redirect it again
|
// if this does not target at all or already targets host, no need to redirect it again
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Card tgt : topSa.getTargets().getTargetCards()) {
|
for (Card tgt : topSa.getTargets().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 false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topSa.getHostCard() != null && !topSa.getHostCard().getController().isOpponentOf(aiPlayer)) {
|
if (topSa.getHostCard() != null && !topSa.getHostCard().getController().isOpponentOf(aiPlayer)) {
|
||||||
// make sure not to redirect our own abilities
|
// make sure not to redirect our own abilities
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
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 false;
|
||||||
}
|
}
|
||||||
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 false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getPayCosts().getCostMana() != null && sa.getPayCosts().getCostMana().getMana().hasPhyrexian()) {
|
if (sa.getPayCosts().getCostMana() != null && sa.getPayCosts().getCostMana().getMana().hasPhyrexian()) {
|
||||||
ManaCost manaCost = sa.getPayCosts().getCostMana().getMana();
|
ManaCost manaCost = sa.getPayCosts().getCostMana().getMana();
|
||||||
int payDamage = manaCost.getPhyrexianCount() * 2;
|
int payDamage = manaCost.getPhyrexianCount() * 2;
|
||||||
// e.g. Spellskite or a creature receiving its ability that requires Phyrexian mana P/U
|
// e.g. Spellskite or a creature receiving its ability that requires Phyrexian mana P/U
|
||||||
int potentialDmg = ComputerUtil.predictDamageFromSpell(topSa, aiPlayer);
|
int potentialDmg = ComputerUtil.predictDamageFromSpell(topSa, aiPlayer);
|
||||||
ManaCost normalizedMana = manaCost.getNormalizedMana();
|
ManaCost normalizedMana = manaCost.getNormalizedMana();
|
||||||
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
|
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
|
||||||
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
|
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
|
||||||
&& topSa.getTargets().getTargets().contains(aiPlayer)) {
|
&& topSa.getTargets().getTargets().contains(aiPlayer)) {
|
||||||
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
|
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(topSa);
|
sa.getTargets().add(topSa);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,405 +1,464 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.Collections;
|
import com.google.common.collect.Iterables;
|
||||||
import java.util.Random;
|
import com.google.common.collect.Lists;
|
||||||
|
import forge.ai.*;
|
||||||
import com.google.common.collect.Iterables;
|
import forge.game.Game;
|
||||||
import com.google.common.collect.Lists;
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.*;
|
||||||
import forge.ai.AiPlayerPredicates;
|
import forge.game.cost.Cost;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.game.player.Player;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.ai.SpecialCardAi;
|
import forge.game.player.PlayerCollection;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.Game;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.game.card.Card;
|
import forge.util.MyRandom;
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardCollectionView;
|
import java.util.Collections;
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.card.CardPredicates;
|
public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||||
import forge.game.cost.Cost;
|
@Override
|
||||||
import forge.game.phase.PhaseType;
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
import forge.game.player.Player;
|
// Change Zone All, can be any type moving from one zone to another
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
final Cost abCost = sa.getPayCosts();
|
||||||
import forge.game.player.PlayerPredicates;
|
final Card source = sa.getHostCard();
|
||||||
import forge.game.spellability.SpellAbility;
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
import forge.game.zone.ZoneType;
|
final Game game = ai.getGame();
|
||||||
import forge.util.MyRandom;
|
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||||
|
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
||||||
public class ChangeZoneAllAi extends SpellAbilityAi {
|
|
||||||
@Override
|
if (abCost != null) {
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
// AI currently disabled for these costs
|
||||||
// Change Zone All, can be any type moving from one zone to another
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
final Cost abCost = sa.getPayCosts();
|
return false;
|
||||||
final Card source = sa.getHostCard();
|
}
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
|
||||||
final Game game = ai.getGame();
|
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
boolean aiLogicAllowsDiscard = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("DiscardAll");
|
||||||
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
|
||||||
|
if (!aiLogicAllowsDiscard) {
|
||||||
if (abCost != null) {
|
return false;
|
||||||
// AI currently disabled for these costs
|
}
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
}
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
// prevent run-away activations - first time will always return true
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
return false;
|
|
||||||
}
|
// TODO targeting with ChangeZoneAll
|
||||||
}
|
// really two types of targeting.
|
||||||
|
// Target Player has all their types change zones
|
||||||
final Random r = MyRandom.getRandom();
|
// or target permanent and do something relative to that permanent
|
||||||
// prevent run-away activations - first time will always return true
|
// ex. "Return all Auras attached to target"
|
||||||
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
// ex. "Return all blocking/blocked by target creature"
|
||||||
|
|
||||||
// TODO targeting with ChangeZoneAll
|
CardCollectionView oppType = CardLists.filterControlledBy(game.getCardsIn(origin), ai.getOpponents());
|
||||||
// really two types of targeting.
|
CardCollectionView computerType = ai.getCardsIn(origin);
|
||||||
// Target Player has all their types change zones
|
|
||||||
// or target permanent and do something relative to that permanent
|
// Ugin check need to be done before filterListByType because of ChosenX
|
||||||
// ex. "Return all Auras attached to target"
|
// Ugin AI: always try to sweep before considering +1
|
||||||
// ex. "Return all blocking/blocked by target creature"
|
if (sourceName.equals("Ugin, the Spirit Dragon")) {
|
||||||
|
return SpecialCardAi.UginTheSpiritDragon.considerPWAbilityPriority(ai, sa, origin, oppType, computerType);
|
||||||
CardCollectionView oppType = CardLists.filterControlledBy(game.getCardsIn(origin), ai.getOpponents());
|
}
|
||||||
CardCollectionView computerType = ai.getCardsIn(origin);
|
|
||||||
|
oppType = AbilityUtils.filterListByType(oppType, sa.getParam("ChangeType"), sa);
|
||||||
// Ugin check need to be done before filterListByType because of ChosenX
|
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
||||||
// Ugin AI: always try to sweep before considering +1
|
|
||||||
if (sourceName.equals("Ugin, the Spirit Dragon")) {
|
if ("LivingDeath".equals(sa.getParam("AILogic"))) {
|
||||||
return SpecialCardAi.UginTheSpiritDragon.considerPWAbilityPriority(ai, sa, origin, oppType, computerType);
|
// Living Death AI
|
||||||
}
|
return SpecialCardAi.LivingDeath.consider(ai, sa);
|
||||||
|
} else if ("Timetwister".equals(sa.getParam("AILogic"))) {
|
||||||
oppType = AbilityUtils.filterListByType(oppType, sa.getParam("ChangeType"), sa);
|
// Timetwister AI
|
||||||
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
return SpecialCardAi.Timetwister.consider(ai, sa);
|
||||||
|
} else if ("RetDiscardedThisTurn".equals(sa.getParam("AILogic"))) {
|
||||||
// Living Death AI
|
// e.g. Shadow of the Grave
|
||||||
if ("LivingDeath".equals(sa.getParam("AILogic"))) {
|
return ai.getNumDiscardedThisTurn() > 0 && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN);
|
||||||
return SpecialCardAi.LivingDeath.consider(ai, sa);
|
} else if ("ExileGraveyards".equals(sa.getParam("AILogic"))) {
|
||||||
}
|
for (Player opp : ai.getOpponents()) {
|
||||||
|
CardCollectionView cardsGY = opp.getCardsIn(ZoneType.Graveyard);
|
||||||
// Timetwister AI
|
CardCollection creats = CardLists.filter(cardsGY, CardPredicates.Presets.CREATURES);
|
||||||
if ("Timetwister".equals(sa.getParam("AILogic"))) {
|
|
||||||
return SpecialCardAi.Timetwister.consider(ai, sa);
|
if (opp.hasDelirium() || opp.hasThreshold() || creats.size() >= 5) {
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
// TODO improve restrictions on when the AI would want to use this
|
}
|
||||||
// spBounceAll has some AI we can compare to.
|
return false;
|
||||||
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
|
} else if ("ManifestCreatsFromGraveyard".equals(sa.getParam("AILogic"))) {
|
||||||
if (!sa.usesTargeting()) {
|
PlayerCollection players = new PlayerCollection();
|
||||||
// TODO: improve logic for non-targeted SAs of this type (most are currently RemAIDeck, e.g. Memory Jar, Timetwister)
|
players.addAll(ai.getOpponents());
|
||||||
return true;
|
players.add(ai);
|
||||||
} else {
|
int maxSize = 1;
|
||||||
// search targetable Opponents
|
for (Player player : players) {
|
||||||
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(), PlayerPredicates.isTargetableBy(sa));
|
Player bestTgt = null;
|
||||||
|
if (player.canBeTargetedBy(sa)) {
|
||||||
// get the one with the most handsize
|
CardCollectionView cardsGY = CardLists.filter(player.getCardsIn(ZoneType.Graveyard),
|
||||||
Player oppTarget = Collections.max(Lists.newArrayList(oppList), PlayerPredicates.compareByZoneSize(origin));
|
CardPredicates.Presets.CREATURES);
|
||||||
|
if (cardsGY.size() > maxSize) {
|
||||||
// set the target
|
maxSize = cardsGY.size();
|
||||||
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
|
bestTgt = player;
|
||||||
sa.resetTargets();
|
}
|
||||||
sa.getTargets().add(oppTarget);
|
}
|
||||||
} else {
|
|
||||||
return false;
|
if (bestTgt != null) {
|
||||||
}
|
sa.resetTargets();
|
||||||
}
|
sa.getTargets().add(bestTgt);
|
||||||
} else if (origin.equals(ZoneType.Battlefield)) {
|
return true;
|
||||||
// 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
|
return false;
|
||||||
// occur
|
}
|
||||||
// if only creatures are affected evaluate both lists and pass only
|
|
||||||
// if human creatures are more valuable
|
// TODO improve restrictions on when the AI would want to use this
|
||||||
if (sa.usesTargeting()) {
|
// spBounceAll has some AI we can compare to.
|
||||||
// search targetable Opponents
|
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
|
||||||
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
if (!sa.usesTargeting()) {
|
||||||
PlayerPredicates.isTargetableBy(sa));
|
// TODO: improve logic for non-targeted SAs of this type (most are currently RemAIDeck, e.g. Memory Jar)
|
||||||
|
return true;
|
||||||
// get the one with the most in graveyard
|
} else {
|
||||||
// zone is visible so evaluate which would be hurt the most
|
// search targetable Opponents
|
||||||
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
|
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(), PlayerPredicates.isTargetableBy(sa));
|
||||||
PlayerPredicates.compareByZoneSize(origin));
|
|
||||||
|
// get the one with the most handsize
|
||||||
// set the target
|
Player oppTarget = Collections.max(Lists.newArrayList(oppList), PlayerPredicates.compareByZoneSize(origin));
|
||||||
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
|
||||||
sa.resetTargets();
|
// set the target
|
||||||
sa.getTargets().add(oppTarget);
|
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||||
} else {
|
sa.resetTargets();
|
||||||
return false;
|
sa.getTargets().add(oppTarget);
|
||||||
}
|
} else {
|
||||||
computerType = new CardCollection();
|
return false;
|
||||||
}
|
}
|
||||||
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
|
}
|
||||||
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
} else if (origin.equals(ZoneType.Battlefield)) {
|
||||||
if ((ComputerUtilCard.evaluateCreatureList(computerType) + 200) >= ComputerUtilCard
|
// this statement is assuming the AI is trying to use this spell
|
||||||
.evaluateCreatureList(oppType)) {
|
// offensively
|
||||||
return false;
|
// if the AI is using it defensively, then something else needs to
|
||||||
}
|
// occur
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human
|
// if only creatures are affected evaluate both lists and pass only
|
||||||
// permanents are more valuable
|
// if human creatures are more valuable
|
||||||
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + 3) >= ComputerUtilCard
|
if (sa.usesTargeting()) {
|
||||||
.evaluatePermanentList(oppType)) {
|
// search targetable Opponents
|
||||||
return false;
|
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
||||||
}
|
PlayerPredicates.isTargetableBy(sa));
|
||||||
|
|
||||||
// Don't cast during main1?
|
// get the one with the most in graveyard
|
||||||
if (game.getPhaseHandler().is(PhaseType.MAIN1, ai)) {
|
// zone is visible so evaluate which would be hurt the most
|
||||||
return false;
|
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
|
||||||
}
|
PlayerPredicates.compareByZoneSize(origin));
|
||||||
} else if (origin.equals(ZoneType.Graveyard)) {
|
|
||||||
if (sa.usesTargeting()) {
|
// set the target
|
||||||
// search targetable Opponents
|
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
||||||
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
sa.resetTargets();
|
||||||
PlayerPredicates.isTargetableBy(sa));
|
sa.getTargets().add(oppTarget);
|
||||||
|
} else {
|
||||||
if (Iterables.isEmpty(oppList)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
computerType = new CardCollection();
|
||||||
|
}
|
||||||
// get the one with the most in graveyard
|
|
||||||
// zone is visible so evaluate which would be hurt the most
|
int creatureEvalThreshold = 200; // value difference (in evaluateCreatureList units)
|
||||||
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
|
int nonCreatureEvalThreshold = 3; // CMC difference
|
||||||
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
|
if (ai.getController().isAI()) {
|
||||||
|
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
// set the target
|
if (destination == ZoneType.Hand) {
|
||||||
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
creatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF);
|
||||||
sa.resetTargets();
|
nonCreatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF);
|
||||||
sa.getTargets().add(oppTarget);
|
} else {
|
||||||
} else {
|
creatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF);
|
||||||
return false;
|
nonCreatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (origin.equals(ZoneType.Exile)) {
|
|
||||||
String logic = sa.getParam("AILogic");
|
// mass zone change for creatures: if in dire danger, do it; otherwise, only do it if the opponent's
|
||||||
|
// creatures are better in value
|
||||||
if (logic != null && logic.startsWith("DiscardAllAndRetExiled")) {
|
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
|
||||||
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(source)).size();
|
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
||||||
int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
if (game.getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) {
|
||||||
|
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
// minimum card advantage unless the hand will be fully reloaded
|
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
|
||||||
int minAdv = logic.contains(".minAdv") ? Integer.parseInt(logic.substring(logic.indexOf(".minAdv") + 7)) : 0;
|
// Life is in serious danger, return all creatures from the battlefield to wherever
|
||||||
|
// so they don't deal lethal damage
|
||||||
return (curHandSize + minAdv - 1 < numExiledWithSrc) || (numExiledWithSrc >= ai.getMaxHandSize());
|
return true;
|
||||||
}
|
}
|
||||||
} else if (origin.equals(ZoneType.Stack)) {
|
}
|
||||||
// time stop can do something like this:
|
if ((ComputerUtilCard.evaluateCreatureList(computerType) + creatureEvalThreshold) >= ComputerUtilCard
|
||||||
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
|
.evaluateCreatureList(oppType)) {
|
||||||
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup
|
return false;
|
||||||
// otherwise, this situation doesn't exist
|
}
|
||||||
return false;
|
} // mass zone change for non-creatures: evaluate both lists by CMC and pass only if human
|
||||||
}
|
// permanents are more valuable
|
||||||
|
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + nonCreatureEvalThreshold) >= ComputerUtilCard
|
||||||
if (destination.equals(ZoneType.Battlefield)) {
|
.evaluatePermanentList(oppType)) {
|
||||||
if (sa.getParam("GainControl") != null) {
|
return false;
|
||||||
// Check if the cards are valuable enough
|
}
|
||||||
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
|
|
||||||
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
// Don't cast during main1?
|
||||||
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
|
if (game.getPhaseHandler().is(PhaseType.MAIN1, ai)) {
|
||||||
.evaluateCreatureList(oppType)) < 400) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
} else if (origin.equals(ZoneType.Graveyard)) {
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human
|
if (sa.usesTargeting()) {
|
||||||
// permanents are less valuable
|
// search targetable Opponents
|
||||||
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
|
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
||||||
.evaluatePermanentList(oppType)) < 6) {
|
PlayerPredicates.isTargetableBy(sa));
|
||||||
return false;
|
|
||||||
}
|
if (Iterables.isEmpty(oppList)) {
|
||||||
} else {
|
return false;
|
||||||
// don't activate if human gets more back than AI does
|
}
|
||||||
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
|
|
||||||
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
// get the one with the most in graveyard
|
||||||
if (ComputerUtilCard.evaluateCreatureList(computerType) <= (ComputerUtilCard
|
// zone is visible so evaluate which would be hurt the most
|
||||||
.evaluateCreatureList(oppType) + 100)) {
|
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
|
||||||
return false;
|
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
|
||||||
}
|
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human
|
// set the target
|
||||||
// permanents are less valuable
|
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
||||||
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= (ComputerUtilCard
|
sa.resetTargets();
|
||||||
.evaluatePermanentList(oppType) + 2)) {
|
sa.getTargets().add(oppTarget);
|
||||||
return false;
|
} else {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (origin.equals(ZoneType.Exile)) {
|
||||||
return (((r.nextFloat() < .8) || sa.isTrigger()) && chance);
|
String logic = sa.getParam("AILogic");
|
||||||
}
|
|
||||||
|
if (logic != null && logic.startsWith("DiscardAllAndRetExiled")) {
|
||||||
/**
|
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(source)).size();
|
||||||
* <p>
|
int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
||||||
* changeZoneAllPlayDrawbackAI.
|
|
||||||
* </p>
|
// minimum card advantage unless the hand will be fully reloaded
|
||||||
* @param sa
|
int minAdv = logic.contains(".minAdv") ? Integer.parseInt(logic.substring(logic.indexOf(".minAdv") + 7)) : 0;
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
|
||||||
* @param af
|
if (numExiledWithSrc > curHandSize) {
|
||||||
* a {@link forge.game.ability.AbilityFactory} object.
|
if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(source)) {
|
||||||
*
|
// Try to gain some card advantage if the card will die anyway
|
||||||
* @return a boolean.
|
// TODO: ideally, should evaluate the hand value and not discard good hands to it
|
||||||
*/
|
return true;
|
||||||
@Override
|
}
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
}
|
||||||
// if putting cards from hand to library and parent is drawing cards
|
|
||||||
// make sure this will actually do something:
|
return (curHandSize + minAdv - 1 < numExiledWithSrc) || (numExiledWithSrc >= ai.getMaxHandSize());
|
||||||
|
}
|
||||||
return true;
|
} else if (origin.equals(ZoneType.Stack)) {
|
||||||
}
|
// time stop can do something like this:
|
||||||
|
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
|
||||||
/* (non-Javadoc)
|
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup
|
||||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
// otherwise, this situation doesn't exist
|
||||||
*/
|
return false;
|
||||||
@Override
|
}
|
||||||
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
|
||||||
final Card source = sa.getHostCard();
|
if (destination.equals(ZoneType.Battlefield)) {
|
||||||
final String hostName = source.getName();
|
if (sa.getParam("GainControl") != null) {
|
||||||
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
// Check if the cards are valuable enough
|
||||||
|
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
|
||||||
if (hostName.equals("Dawnbreak Reclaimer")) {
|
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
||||||
final CardCollectionView cards = AbilityUtils.filterListByType(ai.getGame().getCardsIn(origin), sa.getParam("ChangeType"), sa);
|
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
|
||||||
|
.evaluateCreatureList(oppType)) < 400) {
|
||||||
// AI gets nothing
|
return false;
|
||||||
final CardCollection aiCards = CardLists.filterControlledBy(cards, ai);
|
}
|
||||||
if (aiCards.isEmpty())
|
} // otherwise evaluate both lists by CMC and pass only if human
|
||||||
return false;
|
// permanents are less valuable
|
||||||
|
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
|
||||||
// Human gets nothing
|
.evaluatePermanentList(oppType)) < 6) {
|
||||||
final CardCollection humanCards = CardLists.filterControlledBy(cards, ai.getOpponents());
|
return false;
|
||||||
if (humanCards.isEmpty())
|
}
|
||||||
return true;
|
} else {
|
||||||
|
// don't activate if human gets more back than AI does
|
||||||
// if AI creature is better than Human Creature
|
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
|
||||||
if (ComputerUtilCard.evaluateCreatureList(aiCards) >= ComputerUtilCard
|
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
||||||
.evaluateCreatureList(humanCards)) {
|
if (ComputerUtilCard.evaluateCreatureList(computerType) <= (ComputerUtilCard
|
||||||
return true;
|
.evaluateCreatureList(oppType) + 100)) {
|
||||||
}
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
} // otherwise evaluate both lists by CMC and pass only if human
|
||||||
return true;
|
// permanents are less valuable
|
||||||
}
|
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= (ComputerUtilCard
|
||||||
|
.evaluatePermanentList(oppType) + 2)) {
|
||||||
@Override
|
return false;
|
||||||
protected boolean doTriggerAINoCost(Player ai, final SpellAbility sa, boolean mandatory) {
|
}
|
||||||
// Change Zone All, can be any type moving from one zone to another
|
}
|
||||||
|
}
|
||||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
|
||||||
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
return (((MyRandom.getRandom().nextFloat() < .8) || sa.isTrigger()) && chance);
|
||||||
|
}
|
||||||
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
|
/**
|
||||||
// Profaner from exile without paying its mana cost. Otherwise the card is marked RemAIDeck and there is no
|
* <p>
|
||||||
// specific AI to support playing it in a smarter way. Feel free to expand.
|
* changeZoneAllPlayDrawbackAI.
|
||||||
return !CardLists.filter(ai.getOpponents().getCardsIn(origin), CardPredicates.Presets.CREATURES).isEmpty();
|
* </p>
|
||||||
}
|
* @param sa
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
CardCollectionView humanType = CardLists.filterControlledBy(ai.getGame().getCardsIn(origin), ai.getOpponents());
|
* @param aiPlayer
|
||||||
humanType = AbilityUtils.filterListByType(humanType, sa.getParam("ChangeType"), sa);
|
* a {@link forge.game.player.Player} object.
|
||||||
|
*
|
||||||
CardCollectionView computerType = ai.getCardsIn(origin);
|
* @return a boolean.
|
||||||
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
*/
|
||||||
|
@Override
|
||||||
// TODO improve restrictions on when the AI would want to use this
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
// spBounceAll has some AI we can compare to.
|
// if putting cards from hand to library and parent is drawing cards
|
||||||
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
|
// make sure this will actually do something:
|
||||||
if (sa.usesTargeting()) {
|
|
||||||
// search targetable Opponents
|
return true;
|
||||||
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
}
|
||||||
PlayerPredicates.isTargetableBy(sa));
|
|
||||||
|
/* (non-Javadoc)
|
||||||
// get the one with the most handsize
|
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||||
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
|
*/
|
||||||
PlayerPredicates.compareByZoneSize(origin));
|
@Override
|
||||||
|
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
// set the target
|
final Card source = sa.getHostCard();
|
||||||
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
|
final String hostName = source.getName();
|
||||||
sa.resetTargets();
|
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
||||||
sa.getTargets().add(oppTarget);
|
|
||||||
} else {
|
if (hostName.equals("Dawnbreak Reclaimer")) {
|
||||||
return false;
|
final CardCollectionView cards = AbilityUtils.filterListByType(ai.getGame().getCardsIn(origin), sa.getParam("ChangeType"), sa);
|
||||||
}
|
|
||||||
}
|
// AI gets nothing
|
||||||
} else if (origin.equals(ZoneType.Battlefield)) {
|
final CardCollection aiCards = CardLists.filterControlledBy(cards, ai);
|
||||||
// if mandatory, no need to evaluate
|
if (aiCards.isEmpty())
|
||||||
if (mandatory) {
|
return false;
|
||||||
return true;
|
|
||||||
}
|
// Human gets nothing
|
||||||
// this statement is assuming the AI is trying to use this spell offensively
|
final CardCollection humanCards = CardLists.filterControlledBy(cards, ai.getOpponents());
|
||||||
// if the AI is using it defensively, then something else needs to occur
|
if (humanCards.isEmpty())
|
||||||
// if only creatures are affected evaluate both lists and pass only
|
return true;
|
||||||
// if human creatures are more valuable
|
|
||||||
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) {
|
// if AI creature is better than Human Creature
|
||||||
if (ComputerUtilCard.evaluateCreatureList(computerType) >= ComputerUtilCard
|
if (ComputerUtilCard.evaluateCreatureList(aiCards) >= ComputerUtilCard
|
||||||
.evaluateCreatureList(humanType)) {
|
.evaluateCreatureList(humanCards)) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human
|
return false;
|
||||||
// permanents are more valuable
|
}
|
||||||
else if (ComputerUtilCard.evaluatePermanentList(computerType) >= ComputerUtilCard
|
return true;
|
||||||
.evaluatePermanentList(humanType)) {
|
}
|
||||||
return false;
|
|
||||||
}
|
@Override
|
||||||
} else if (origin.equals(ZoneType.Graveyard)) {
|
protected boolean doTriggerAINoCost(Player ai, final SpellAbility sa, boolean mandatory) {
|
||||||
if (sa.usesTargeting()) {
|
// Change Zone All, can be any type moving from one zone to another
|
||||||
// search targetable Opponents
|
|
||||||
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||||
PlayerPredicates.isTargetableBy(sa));
|
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
||||||
|
|
||||||
// get the one with the most in graveyard
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Profaner of the Dead")) {
|
||||||
// zone is visible so evaluate which would be hurt the most
|
// TODO: this is a stub to prevent the AI from crashing the game when, for instance, playing the opponent's
|
||||||
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
|
// Profaner from exile without paying its mana cost. Otherwise the card is marked RemAIDeck and there is no
|
||||||
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
|
// specific AI to support playing it in a smarter way. Feel free to expand.
|
||||||
|
return !CardLists.filter(ai.getOpponents().getCardsIn(origin), CardPredicates.Presets.CREATURES).isEmpty();
|
||||||
// set the target
|
}
|
||||||
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
|
||||||
sa.resetTargets();
|
CardCollectionView humanType = CardLists.filterControlledBy(ai.getGame().getCardsIn(origin), ai.getOpponents());
|
||||||
sa.getTargets().add(oppTarget);
|
humanType = AbilityUtils.filterListByType(humanType, sa.getParam("ChangeType"), sa);
|
||||||
} else {
|
|
||||||
return false;
|
CardCollectionView computerType = ai.getCardsIn(origin);
|
||||||
}
|
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
||||||
}
|
|
||||||
} else if (origin.equals(ZoneType.Exile)) {
|
// TODO improve restrictions on when the AI would want to use this
|
||||||
|
// spBounceAll has some AI we can compare to.
|
||||||
} else if (origin.equals(ZoneType.Stack)) {
|
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
|
||||||
// time stop can do something like this:
|
if (sa.usesTargeting()) {
|
||||||
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
|
// search targetable Opponents
|
||||||
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup
|
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
||||||
// otherwise, this situation doesn't exist
|
PlayerPredicates.isTargetableBy(sa));
|
||||||
return false;
|
|
||||||
}
|
// get the one with the most handsize
|
||||||
|
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
|
||||||
if (destination.equals(ZoneType.Battlefield)) {
|
PlayerPredicates.compareByZoneSize(origin));
|
||||||
// if mandatory, no need to evaluate
|
|
||||||
if (mandatory) {
|
// set the target
|
||||||
return true;
|
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||||
}
|
sa.resetTargets();
|
||||||
if (sa.getParam("GainControl") != null) {
|
sa.getTargets().add(oppTarget);
|
||||||
// Check if the cards are valuable enough
|
} else {
|
||||||
if ((CardLists.getNotType(humanType, "Creature").size() == 0) && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
return false;
|
||||||
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
|
}
|
||||||
.evaluateCreatureList(humanType)) < 1) {
|
}
|
||||||
return false;
|
} else if (origin.equals(ZoneType.Battlefield)) {
|
||||||
}
|
// if mandatory, no need to evaluate
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human
|
if (mandatory) {
|
||||||
// permanents are less valuable
|
return true;
|
||||||
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
|
}
|
||||||
.evaluatePermanentList(humanType)) < 1) {
|
// this statement is assuming the AI is trying to use this spell offensively
|
||||||
return false;
|
// if the AI is using it defensively, then something else needs to occur
|
||||||
}
|
// if only creatures are affected evaluate both lists and pass only
|
||||||
} else {
|
// if human creatures are more valuable
|
||||||
// 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())) {
|
if (ComputerUtilCard.evaluateCreatureList(computerType) >= ComputerUtilCard
|
||||||
if (ComputerUtilCard.evaluateCreatureList(computerType) <= ComputerUtilCard
|
.evaluateCreatureList(humanType)) {
|
||||||
.evaluateCreatureList(humanType)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
} // otherwise evaluate both lists by CMC and pass only if human
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human
|
// permanents are more valuable
|
||||||
// permanents are less valuable
|
else if (ComputerUtilCard.evaluatePermanentList(computerType) >= ComputerUtilCard
|
||||||
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= ComputerUtilCard
|
.evaluatePermanentList(humanType)) {
|
||||||
.evaluatePermanentList(humanType)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
} else if (origin.equals(ZoneType.Graveyard)) {
|
||||||
}
|
if (sa.usesTargeting()) {
|
||||||
}
|
// search targetable Opponents
|
||||||
|
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
|
||||||
return true;
|
PlayerPredicates.isTargetableBy(sa));
|
||||||
}
|
|
||||||
|
// get the one with the most in graveyard
|
||||||
}
|
// zone is visible so evaluate which would be hurt the most
|
||||||
|
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
|
||||||
|
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
|
||||||
|
|
||||||
|
// set the target
|
||||||
|
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(oppTarget);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (origin.equals(ZoneType.Exile)) {
|
||||||
|
|
||||||
|
} else if (origin.equals(ZoneType.Stack)) {
|
||||||
|
// time stop can do something like this:
|
||||||
|
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
|
||||||
|
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup
|
||||||
|
// otherwise, this situation doesn't exist
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destination.equals(ZoneType.Battlefield)) {
|
||||||
|
// if mandatory, no need to evaluate
|
||||||
|
if (mandatory) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (sa.getParam("GainControl") != null) {
|
||||||
|
// Check if the cards are valuable enough
|
||||||
|
if ((CardLists.getNotType(humanType, "Creature").size() == 0) && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
||||||
|
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
|
||||||
|
.evaluateCreatureList(humanType)) < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // otherwise evaluate both lists by CMC and pass only if human
|
||||||
|
// permanents are less valuable
|
||||||
|
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
|
||||||
|
.evaluatePermanentList(humanType)) < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// don't activate if human gets more back than AI does
|
||||||
|
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) {
|
||||||
|
if (ComputerUtilCard.evaluateCreatureList(computerType) <= ComputerUtilCard
|
||||||
|
.evaluateCreatureList(humanType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // otherwise evaluate both lists by CMC and pass only if human
|
||||||
|
// permanents are less valuable
|
||||||
|
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= ComputerUtilCard
|
||||||
|
.evaluatePermanentList(humanType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,244 +1,238 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import com.google.common.collect.Lists;
|
||||||
import java.util.Random;
|
import forge.ai.*;
|
||||||
|
import forge.game.ability.effects.CharmEffect;
|
||||||
import com.google.common.collect.Lists;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.ai.AiController;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.ai.AiPlayDecision;
|
import forge.util.Aggregates;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.util.MyRandom;
|
||||||
import forge.ai.PlayerControllerAi;
|
import forge.util.collect.FCollection;
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.ability.effects.CharmEffect;
|
import java.util.List;
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.spellability.AbilitySub;
|
public class CharmAi extends SpellAbilityAi {
|
||||||
import forge.game.spellability.SpellAbility;
|
@Override
|
||||||
import forge.util.Aggregates;
|
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
import forge.util.MyRandom;
|
// sa is Entwined, no need for extra logic
|
||||||
import forge.util.collect.FCollection;
|
if (sa.isEntwine()) {
|
||||||
|
return true;
|
||||||
public class CharmAi extends SpellAbilityAi {
|
}
|
||||||
@Override
|
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
|
||||||
// sa is Entwined, no need for extra logic
|
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
|
||||||
if (sa.isEntwine()) {
|
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
|
||||||
return true;
|
|
||||||
}
|
// Reset the chosen list otherwise it will be locked in forever by earlier calls
|
||||||
|
sa.setChosenList(null);
|
||||||
final Random r = MyRandom.getRandom();
|
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
|
||||||
|
List<AbilitySub> chosenList;
|
||||||
final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
|
|
||||||
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
|
if (!ai.equals(sa.getActivatingPlayer())) {
|
||||||
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
|
// This branch is for "An Opponent chooses" Charm spells from Alliances
|
||||||
|
// Current just choose the first available spell, which seem generally less disastrous for the AI.
|
||||||
// Reset the chosen list otherwise it will be locked in forever by earlier calls
|
//return choices.subList(0, 1);
|
||||||
sa.setChosenList(null);
|
chosenList = choices.subList(1, choices.size());
|
||||||
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
|
} else if ("Triskaidekaphobia".equals(ComputerUtilAbility.getAbilitySourceName(sa))) {
|
||||||
List<AbilitySub> chosenList;
|
chosenList = chooseTriskaidekaphobia(choices, ai);
|
||||||
|
} else {
|
||||||
if (!ai.equals(sa.getActivatingPlayer())) {
|
/*
|
||||||
// This branch is for "An Opponent chooses" Charm spells from Alliances
|
* The generic chooseOptionsAi uses canPlayAi() to determine good choices
|
||||||
// Current just choose the first available spell, which seem generally less disastrous for the AI.
|
* which means most "bonus" effects like life-gain and random pumps will
|
||||||
//return choices.subList(0, 1);
|
* usually not be chosen. This is designed to force the AI to only select
|
||||||
chosenList = choices.subList(1, choices.size());
|
* the best choice(s) since it does not actually know if it can pay for
|
||||||
} else if ("Triskaidekaphobia".equals(ComputerUtilAbility.getAbilitySourceName(sa))) {
|
* "bonus" choices (eg. Entwine/Escalate).
|
||||||
chosenList = chooseTriskaidekaphobia(choices, ai);
|
* chooseMultipleOptionsAi() uses "AILogic$Good" tags to manually identify
|
||||||
} else {
|
* bonus choice(s) for the AI otherwise it might be too hard to ever fulfil
|
||||||
/*
|
* minimum choice requirements with canPlayAi() alone.
|
||||||
* The generic chooseOptionsAi uses canPlayAi() to determine good choices
|
*/
|
||||||
* which means most "bonus" effects like life-gain and random pumps will
|
chosenList = min > 1 ? chooseMultipleOptionsAi(choices, ai, min)
|
||||||
* usually not be chosen. This is designed to force the AI to only select
|
: chooseOptionsAi(choices, ai, timingRight, num, min, sa.hasParam("CanRepeatModes"));
|
||||||
* the best choice(s) since it does not actually know if it can pay for
|
}
|
||||||
* "bonus" choices (eg. Entwine/Escalate).
|
|
||||||
* chooseMultipleOptionsAi() uses "AILogic$Good" tags to manually identify
|
if (chosenList.isEmpty()) {
|
||||||
* bonus choice(s) for the AI otherwise it might be too hard to ever fulfil
|
if (timingRight) {
|
||||||
* minimum choice requirements with canPlayAi() alone.
|
// Set minimum choices for triggers where chooseMultipleOptionsAi() returns null
|
||||||
*/
|
chosenList = chooseOptionsAi(choices, ai, true, num, min, sa.hasParam("CanRepeatModes"));
|
||||||
chosenList = min > 1 ? chooseMultipleOptionsAi(choices, ai, min)
|
} else {
|
||||||
: chooseOptionsAi(choices, ai, timingRight, num, min, sa.hasParam("CanRepeatModes"));
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (chosenList.isEmpty()) {
|
sa.setChosenList(chosenList);
|
||||||
if (timingRight) {
|
|
||||||
// Set minimum choices for triggers where chooseMultipleOptionsAi() returns null
|
// prevent run-away activations - first time will always return true
|
||||||
chosenList = chooseOptionsAi(choices, ai, true, num, min, sa.hasParam("CanRepeatModes"));
|
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
} else {
|
}
|
||||||
return false;
|
|
||||||
}
|
private List<AbilitySub> chooseOptionsAi(List<AbilitySub> choices, final Player ai, boolean isTrigger, int num,
|
||||||
}
|
int min, boolean allowRepeat) {
|
||||||
sa.setChosenList(chosenList);
|
List<AbilitySub> chosenList = Lists.newArrayList();
|
||||||
|
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||||
// prevent run-away activations - first time will always return true
|
// First pass using standard canPlayAi() for good choices
|
||||||
return r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
for (AbilitySub sub : choices) {
|
||||||
}
|
sub.setActivatingPlayer(ai);
|
||||||
|
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
||||||
private List<AbilitySub> chooseOptionsAi(List<AbilitySub> choices, final Player ai, boolean isTrigger, int num,
|
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
|
||||||
int min, boolean allowRepeat) {
|
chosenList.add(sub);
|
||||||
List<AbilitySub> chosenList = Lists.newArrayList();
|
if (chosenList.size() == num) {
|
||||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
return chosenList; // maximum choices reached
|
||||||
// First pass using standard canPlayAi() for good choices
|
}
|
||||||
for (AbilitySub sub : choices) {
|
}
|
||||||
sub.setActivatingPlayer(ai);
|
}
|
||||||
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
if (isTrigger && chosenList.size() < min) {
|
||||||
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
|
// Second pass using doTrigger(false) to fulfil minimum choice
|
||||||
chosenList.add(sub);
|
choices.removeAll(chosenList);
|
||||||
if (chosenList.size() == num) {
|
for (AbilitySub sub : choices) {
|
||||||
return chosenList; // maximum choices reached
|
sub.setActivatingPlayer(ai);
|
||||||
}
|
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
||||||
}
|
if (aic.doTrigger(sub, false)) {
|
||||||
}
|
chosenList.add(sub);
|
||||||
if (isTrigger && chosenList.size() < min) {
|
if (chosenList.size() == min) {
|
||||||
// Second pass using doTrigger(false) to fulfil minimum choice
|
return chosenList;
|
||||||
choices.removeAll(chosenList);
|
}
|
||||||
for (AbilitySub sub : choices) {
|
}
|
||||||
sub.setActivatingPlayer(ai);
|
}
|
||||||
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
// Third pass using doTrigger(true) to force fill minimum choices
|
||||||
if (aic.doTrigger(sub, false)) {
|
if (chosenList.size() < min) {
|
||||||
chosenList.add(sub);
|
choices.removeAll(chosenList);
|
||||||
if (chosenList.size() == min) {
|
for (AbilitySub sub : choices) {
|
||||||
return chosenList;
|
sub.setActivatingPlayer(ai);
|
||||||
}
|
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
||||||
}
|
if (aic.doTrigger(sub, true)) {
|
||||||
}
|
chosenList.add(sub);
|
||||||
// Third pass using doTrigger(true) to force fill minimum choices
|
if (chosenList.size() == min) {
|
||||||
if (chosenList.size() < min) {
|
break;
|
||||||
choices.removeAll(chosenList);
|
}
|
||||||
for (AbilitySub sub : choices) {
|
}
|
||||||
sub.setActivatingPlayer(ai);
|
}
|
||||||
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
}
|
||||||
if (aic.doTrigger(sub, true)) {
|
}
|
||||||
chosenList.add(sub);
|
if (chosenList.size() < min) {
|
||||||
if (chosenList.size() == min) {
|
chosenList.clear(); // not enough choices
|
||||||
break;
|
}
|
||||||
}
|
return chosenList;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
private List<AbilitySub> chooseTriskaidekaphobia(List<AbilitySub> choices, final Player ai) {
|
||||||
}
|
List<AbilitySub> chosenList = Lists.newArrayList();
|
||||||
if (chosenList.size() < min) {
|
if (choices == null || choices.isEmpty()) { return chosenList; }
|
||||||
chosenList.clear(); // not enough choices
|
|
||||||
}
|
AbilitySub gain = choices.get(0);
|
||||||
return chosenList;
|
AbilitySub lose = choices.get(1);
|
||||||
}
|
FCollection<Player> opponents = ai.getOpponents();
|
||||||
|
|
||||||
private List<AbilitySub> chooseTriskaidekaphobia(List<AbilitySub> choices, final Player ai) {
|
boolean oppTainted = false;
|
||||||
List<AbilitySub> chosenList = Lists.newArrayList();
|
boolean allyTainted = ai.isCardInPlay("Tainted Remedy");
|
||||||
AbilitySub gain = choices.get(0);
|
final int aiLife = ai.getLife();
|
||||||
AbilitySub lose = choices.get(1);
|
|
||||||
FCollection<Player> opponents = ai.getOpponents();
|
//Check if Opponent controls Tainted Remedy
|
||||||
|
for (Player p : opponents) {
|
||||||
boolean oppTainted = false;
|
if (p.isCardInPlay("Tainted Remedy")) {
|
||||||
boolean allyTainted = ai.isCardInPlay("Tainted Remedy");
|
oppTainted = true;
|
||||||
final int aiLife = ai.getLife();
|
break;
|
||||||
|
}
|
||||||
//Check if Opponent controls Tainted Remedy
|
}
|
||||||
for (Player p : opponents) {
|
// if ai or ally of ai does control Tainted Remedy, prefer gain life instead of lose
|
||||||
if (p.isCardInPlay("Tainted Remedy")) {
|
if (!allyTainted) {
|
||||||
oppTainted = true;
|
for (Player p : ai.getAllies()) {
|
||||||
break;
|
if (p.isCardInPlay("Tainted Remedy")) {
|
||||||
}
|
allyTainted = true;
|
||||||
}
|
break;
|
||||||
// if ai or ally of ai does control Tainted Remedy, prefer gain life instead of lose
|
}
|
||||||
if (!allyTainted) {
|
}
|
||||||
for (Player p : ai.getAllies()) {
|
}
|
||||||
if (p.isCardInPlay("Tainted Remedy")) {
|
|
||||||
allyTainted = true;
|
if (!ai.canLoseLife() || ai.cantLose()) {
|
||||||
break;
|
// ai cant lose life, or cant lose the game, don't think about others
|
||||||
}
|
chosenList.add(allyTainted ? gain : lose);
|
||||||
}
|
} else if (oppTainted || ai.getGame().isCardInPlay("Rain of Gore")) {
|
||||||
}
|
// Rain of Gore does negate lifegain, so don't benefit the others
|
||||||
|
// same for if a oppoent does control Tainted Remedy
|
||||||
if (!ai.canLoseLife() || ai.cantLose()) {
|
// but if ai cant gain life, the effects are negated
|
||||||
// ai cant lose life, or cant lose the game, don't think about others
|
chosenList.add(ai.canGainLife() ? lose : gain);
|
||||||
chosenList.add(allyTainted ? gain : lose);
|
} else if (ai.getGame().isCardInPlay("Sulfuric Vortex")) {
|
||||||
} else if (oppTainted || ai.getGame().isCardInPlay("Rain of Gore")) {
|
// no life gain, but extra life loss.
|
||||||
// Rain of Gore does negate lifegain, so don't benefit the others
|
if (aiLife >= 17)
|
||||||
// same for if a oppoent does control Tainted Remedy
|
chosenList.add(lose);
|
||||||
// but if ai cant gain life, the effects are negated
|
// try to prevent to get to 13 with extra lose
|
||||||
chosenList.add(ai.canGainLife() ? lose : gain);
|
else if (aiLife < 13 || ((aiLife - 13) % 2) == 1) {
|
||||||
} else if (ai.getGame().isCardInPlay("Sulfuric Vortex")) {
|
chosenList.add(gain);
|
||||||
// no life gain, but extra life loss.
|
} else {
|
||||||
if (aiLife >= 17)
|
chosenList.add(lose);
|
||||||
chosenList.add(lose);
|
}
|
||||||
// try to prevent to get to 13 with extra lose
|
} else if (ai.canGainLife() && aiLife <= 5) {
|
||||||
else if (aiLife < 13 || ((aiLife - 13) % 2) == 1) {
|
// critical Life try to gain more
|
||||||
chosenList.add(gain);
|
chosenList.add(gain);
|
||||||
} else {
|
} else if(!ai.canGainLife() && aiLife == 14 ) {
|
||||||
chosenList.add(lose);
|
// ai cant gain life, but try to avoid falling to 13
|
||||||
}
|
// but if a oppoent does control Tainted Remedy its irrelevant
|
||||||
} else if (ai.canGainLife() && aiLife <= 5) {
|
chosenList.add(oppTainted ? lose : gain);
|
||||||
// critical Life try to gain more
|
} else if (allyTainted) {
|
||||||
chosenList.add(gain);
|
// Tainted Remedy negation logic, try gain instead of lose
|
||||||
} else if(!ai.canGainLife() && aiLife == 14 ) {
|
// because negation does turn it into lose for opponents
|
||||||
// ai cant gain life, but try to avoid falling to 13
|
boolean oppCritical = false;
|
||||||
// but if a oppoent does control Tainted Remedy its irrelevant
|
// an oppoent is Critical = 14, and can't gain life, try to lose life instead
|
||||||
chosenList.add(oppTainted ? lose : gain);
|
// but only if ai doesn't kill itself with that.
|
||||||
} else if (allyTainted) {
|
if (aiLife != 14) {
|
||||||
// Tainted Remedy negation logic, try gain instead of lose
|
for (Player p : opponents) {
|
||||||
// because negation does turn it into lose for opponents
|
if (p.getLife() == 14 && !p.canGainLife() && p.canLoseLife()) {
|
||||||
boolean oppCritical = false;
|
oppCritical = true;
|
||||||
// an oppoent is Critical = 14, and can't gain life, try to lose life instead
|
break;
|
||||||
// but only if ai doesn't kill itself with that.
|
}
|
||||||
if (aiLife != 14) {
|
}
|
||||||
for (Player p : opponents) {
|
}
|
||||||
if (p.getLife() == 14 && !p.canGainLife() && p.canLoseLife()) {
|
chosenList.add(aiLife == 12 || oppCritical ? lose : gain);
|
||||||
oppCritical = true;
|
} else {
|
||||||
break;
|
// normal logic, try to gain life if its critical
|
||||||
}
|
boolean oppCritical = false;
|
||||||
}
|
// an oppoent is Critical = 12, and can gain life, try to gain life instead
|
||||||
}
|
// but only if ai doesn't kill itself with that.
|
||||||
chosenList.add(aiLife == 12 || oppCritical ? lose : gain);
|
if (aiLife != 12) {
|
||||||
} else {
|
for (Player p : opponents) {
|
||||||
// normal logic, try to gain life if its critical
|
if (p.getLife() == 12 && p.canGainLife()) {
|
||||||
boolean oppCritical = false;
|
oppCritical = true;
|
||||||
// an oppoent is Critical = 12, and can gain life, try to gain life instead
|
break;
|
||||||
// but only if ai doesn't kill itself with that.
|
}
|
||||||
if (aiLife != 12) {
|
}
|
||||||
for (Player p : opponents) {
|
}
|
||||||
if (p.getLife() == 12 && p.canGainLife()) {
|
chosenList.add(aiLife == 14 || aiLife <= 10 || oppCritical ? gain : lose);
|
||||||
oppCritical = true;
|
}
|
||||||
break;
|
return chosenList;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
// Choice selection for charms that require multiple choices (eg. Cryptic Command, DTK commands)
|
||||||
chosenList.add(aiLife == 14 || aiLife <= 10 || oppCritical ? gain : lose);
|
private List<AbilitySub> chooseMultipleOptionsAi(List<AbilitySub> choices, final Player ai, int min) {
|
||||||
}
|
AbilitySub goodChoice = null;
|
||||||
return chosenList;
|
List<AbilitySub> chosenList = Lists.newArrayList();
|
||||||
}
|
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||||
|
for (AbilitySub sub : choices) {
|
||||||
// Choice selection for charms that require multiple choices (eg. Cryptic Command, DTK commands)
|
sub.setActivatingPlayer(ai);
|
||||||
private List<AbilitySub> chooseMultipleOptionsAi(List<AbilitySub> choices, final Player ai, int min) {
|
// Assign generic good choice to fill up choices if necessary
|
||||||
AbilitySub goodChoice = null;
|
if ("Good".equals(sub.getParam("AILogic")) && aic.doTrigger(sub, false)) {
|
||||||
List<AbilitySub> chosenList = Lists.newArrayList();
|
goodChoice = sub;
|
||||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
} else {
|
||||||
for (AbilitySub sub : choices) {
|
// Standard canPlayAi()
|
||||||
sub.setActivatingPlayer(ai);
|
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
|
||||||
// Assign generic good choice to fill up choices if necessary
|
chosenList.add(sub);
|
||||||
if ("Good".equals(sub.getParam("AILogic")) && aic.doTrigger(sub, false)) {
|
if (chosenList.size() == min) {
|
||||||
goodChoice = sub;
|
break; // enough choices
|
||||||
} else {
|
}
|
||||||
// Standard canPlayAi()
|
}
|
||||||
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
|
}
|
||||||
chosenList.add(sub);
|
}
|
||||||
if (chosenList.size() == min) {
|
// Add generic good choice if one more choice is needed
|
||||||
break; // enough choices
|
if (chosenList.size() == min - 1 && goodChoice != null) {
|
||||||
}
|
chosenList.add(0, goodChoice); // hack to make Dromoka's Command fight targets work
|
||||||
}
|
}
|
||||||
}
|
if (chosenList.size() != min) {
|
||||||
}
|
chosenList.clear();
|
||||||
// Add generic good choice if one more choice is needed
|
}
|
||||||
if (chosenList.size() == min - 1 && goodChoice != null) {
|
return chosenList;
|
||||||
chosenList.add(0, goodChoice); // hack to make Dromoka's Command fight targets work
|
}
|
||||||
}
|
|
||||||
if (chosenList.size() != min) {
|
@Override
|
||||||
chosenList.clear();
|
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> opponents) {
|
||||||
}
|
return Aggregates.random(opponents);
|
||||||
return chosenList;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> opponents) {
|
|
||||||
return Aggregates.random(opponents);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,267 +1,282 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import forge.ai.ComputerUtilAbility;
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCombat;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.game.Game;
|
import forge.ai.ComputerUtilCombat;
|
||||||
import forge.game.card.Card;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.Game;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
import forge.game.player.Player;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.player.PlayerPredicates;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.util.Aggregates;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerPredicates;
|
||||||
public class ChooseCardAi extends SpellAbilityAi {
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
/**
|
import forge.util.Aggregates;
|
||||||
* The rest of the logic not covered by the canPlayAI template is defined here
|
|
||||||
*/
|
public class ChooseCardAi extends SpellAbilityAi {
|
||||||
@Override
|
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
/**
|
||||||
if (sa.usesTargeting()) {
|
* The rest of the logic not covered by the canPlayAI template is defined here
|
||||||
sa.resetTargets();
|
*/
|
||||||
// search targetable Opponents
|
@Override
|
||||||
final List<Player> oppList = Lists.newArrayList(Iterables.filter(
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
ai.getOpponents(), PlayerPredicates.isTargetableBy(sa)));
|
if (sa.usesTargeting()) {
|
||||||
|
sa.resetTargets();
|
||||||
if (oppList.isEmpty()) {
|
// search targetable Opponents
|
||||||
return false;
|
final List<Player> oppList = Lists.newArrayList(Iterables.filter(
|
||||||
}
|
ai.getOpponents(), PlayerPredicates.isTargetableBy(sa)));
|
||||||
|
|
||||||
sa.getTargets().add(Iterables.getFirst(oppList, null));
|
if (oppList.isEmpty()) {
|
||||||
}
|
return false;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
sa.getTargets().add(Iterables.getFirst(oppList, null));
|
||||||
/**
|
}
|
||||||
* Checks if the AI will play a SpellAbility with the specified AiLogic
|
return true;
|
||||||
*/
|
}
|
||||||
@Override
|
|
||||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
/**
|
||||||
final Card host = sa.getHostCard();
|
* Checks if the AI will play a SpellAbility with the specified AiLogic
|
||||||
final Game game = ai.getGame();
|
*/
|
||||||
ZoneType choiceZone = ZoneType.Battlefield;
|
@Override
|
||||||
if (sa.hasParam("ChoiceZone")) {
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
|
final Card host = sa.getHostCard();
|
||||||
}
|
final Game game = ai.getGame();
|
||||||
CardCollectionView choices = ai.getGame().getCardsIn(choiceZone);
|
ZoneType choiceZone = ZoneType.Battlefield;
|
||||||
if (sa.hasParam("Choices")) {
|
if (sa.hasParam("ChoiceZone")) {
|
||||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
|
||||||
}
|
}
|
||||||
if (sa.hasParam("TargetControls")) {
|
CardCollectionView choices = ai.getGame().getCardsIn(choiceZone);
|
||||||
choices = CardLists.filterControlledBy(choices, ai.getOpponents());
|
if (sa.hasParam("Choices")) {
|
||||||
}
|
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||||
if (aiLogic.equals("AtLeast1") || aiLogic.equals("OppPreferred")) {
|
}
|
||||||
if (choices.isEmpty()) {
|
if (sa.hasParam("TargetControls")) {
|
||||||
return false;
|
choices = CardLists.filterControlledBy(choices, ai.getOpponents());
|
||||||
}
|
}
|
||||||
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
|
if (aiLogic.equals("AtLeast1") || aiLogic.equals("OppPreferred")) {
|
||||||
if (choices.size() < 2) {
|
if (choices.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (aiLogic.equals("Clone") || aiLogic.equals("Vesuva")) {
|
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
|
||||||
final String filter = aiLogic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
if (choices.size() < 2) {
|
||||||
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
return false;
|
||||||
|
}
|
||||||
choices = CardLists.getValidCards(choices, filter, host.getController(), host);
|
} else if (aiLogic.equals("Clone") || aiLogic.equals("Vesuva")) {
|
||||||
if (choices.isEmpty()) {
|
final String filter = aiLogic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
||||||
return false;
|
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
||||||
}
|
|
||||||
} else if (aiLogic.equals("Never")) {
|
choices = CardLists.getValidCards(choices, filter, host.getController(), host);
|
||||||
return false;
|
if (choices.isEmpty()) {
|
||||||
} else if (aiLogic.equals("NeedsPrevention")) {
|
return false;
|
||||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
}
|
||||||
return false;
|
} else if (aiLogic.equals("Never")) {
|
||||||
}
|
return false;
|
||||||
final Combat combat = game.getCombat();
|
} else if (aiLogic.equals("NeedsPrevention")) {
|
||||||
choices = CardLists.filter(choices, new Predicate<Card>() {
|
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
@Override
|
return false;
|
||||||
public boolean apply(final Card c) {
|
}
|
||||||
if (!combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
final Combat combat = game.getCombat();
|
||||||
return false;
|
choices = CardLists.filter(choices, new Predicate<Card>() {
|
||||||
}
|
@Override
|
||||||
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
|
public boolean apply(final Card c) {
|
||||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
|
if (!combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||||
}
|
return false;
|
||||||
});
|
}
|
||||||
if (choices.isEmpty()) {
|
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
|
||||||
return false;
|
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
|
||||||
}
|
}
|
||||||
} else if (aiLogic.equals("Ashiok")) {
|
});
|
||||||
final int loyalty = host.getCounters(CounterType.LOYALTY) - 1;
|
if (choices.isEmpty()) {
|
||||||
for (int i = loyalty; i >= 0; i--) {
|
return false;
|
||||||
host.setSVar("ChosenX", "Number$" + i);
|
}
|
||||||
choices = ai.getGame().getCardsIn(choiceZone);
|
} else if (aiLogic.equals("Ashiok")) {
|
||||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
final int loyalty = host.getCounters(CounterType.LOYALTY) - 1;
|
||||||
if (!choices.isEmpty()) {
|
for (int i = loyalty; i >= 0; i--) {
|
||||||
return true;
|
host.setSVar("ChosenX", "Number$" + i);
|
||||||
}
|
choices = ai.getGame().getCardsIn(choiceZone);
|
||||||
}
|
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||||
|
if (!choices.isEmpty()) {
|
||||||
if (choices.isEmpty()) {
|
return true;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
} else if (aiLogic.equals("RandomNonLand")) {
|
|
||||||
if (CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host).isEmpty()) {
|
if (choices.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (aiLogic.equals("Duneblast")) {
|
} else if (aiLogic.equals("RandomNonLand")) {
|
||||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
if (CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host).isEmpty()) {
|
||||||
CardCollection oppCreatures = ai.getOpponent().getCreaturesInPlay();
|
return false;
|
||||||
aiCreatures = CardLists.getNotKeyword(aiCreatures, "Indestructible");
|
}
|
||||||
oppCreatures = CardLists.getNotKeyword(oppCreatures, "Indestructible");
|
} else if (aiLogic.equals("Duneblast")) {
|
||||||
|
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||||
// Use it as a wrath, when the human creatures threat the ai's life
|
CardCollection oppCreatures = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
|
||||||
if (aiCreatures.isEmpty() && ComputerUtilCombat.sumDamageIfUnblocked(oppCreatures, ai) >= ai.getLife()) {
|
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
|
||||||
return true;
|
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
|
||||||
}
|
|
||||||
|
// Use it as a wrath, when the human creatures threat the ai's life
|
||||||
Card chosen = ComputerUtilCard.getBestCreatureAI(aiCreatures);
|
if (aiCreatures.isEmpty() && ComputerUtilCombat.sumDamageIfUnblocked(oppCreatures, ai) >= ai.getLife()) {
|
||||||
aiCreatures.remove(chosen);
|
return true;
|
||||||
int minGain = 200;
|
}
|
||||||
|
|
||||||
if ((ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) >= ComputerUtilCard
|
Card chosen = ComputerUtilCard.getBestCreatureAI(aiCreatures);
|
||||||
.evaluateCreatureList(oppCreatures)) {
|
aiCreatures.remove(chosen);
|
||||||
return false;
|
int minGain = 200;
|
||||||
}
|
|
||||||
}
|
if ((ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) >= ComputerUtilCard
|
||||||
return true;
|
.evaluateCreatureList(oppCreatures)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
@Override
|
}
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
return true;
|
||||||
if (sa.hasParam("AILogic") && !checkAiLogic(ai, sa, sa.getParam("AILogic"))) {
|
}
|
||||||
return false;
|
|
||||||
}
|
@Override
|
||||||
return checkApiLogic(ai, sa);
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
}
|
if (sa.hasParam("AILogic") && !checkAiLogic(ai, sa, sa.getParam("AILogic"))) {
|
||||||
|
return false;
|
||||||
/* (non-Javadoc)
|
}
|
||||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
return checkApiLogic(ai, sa);
|
||||||
*/
|
}
|
||||||
@Override
|
|
||||||
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
/* (non-Javadoc)
|
||||||
final Card host = sa.getHostCard();
|
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||||
final Player ctrl = host.getController();
|
*/
|
||||||
final String logic = sa.getParam("AILogic");
|
@Override
|
||||||
Card choice = null;
|
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||||
if (logic == null) {
|
final Card host = sa.getHostCard();
|
||||||
// Base Logic is choose "best"
|
final Player ctrl = host.getController();
|
||||||
choice = ComputerUtilCard.getBestAI(options);
|
final String logic = sa.getParam("AILogic");
|
||||||
} else if ("WorstCard".equals(logic)) {
|
Card choice = null;
|
||||||
choice = ComputerUtilCard.getWorstAI(options);
|
if (logic == null) {
|
||||||
} else if (logic.equals("BestBlocker")) {
|
// Base Logic is choose "best"
|
||||||
if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) {
|
choice = ComputerUtilCard.getBestAI(options);
|
||||||
options = CardLists.filter(options, Presets.UNTAPPED);
|
} else if ("WorstCard".equals(logic)) {
|
||||||
}
|
choice = ComputerUtilCard.getWorstAI(options);
|
||||||
choice = ComputerUtilCard.getBestCreatureAI(options);
|
} else if (logic.equals("BestBlocker")) {
|
||||||
} else if (logic.equals("Clone") || logic.equals("Vesuva")) {
|
if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) {
|
||||||
final String filter = logic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary" :
|
options = CardLists.filter(options, Presets.UNTAPPED);
|
||||||
"Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
}
|
||||||
|
choice = ComputerUtilCard.getBestCreatureAI(options);
|
||||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
} else if (logic.equals("Clone") || logic.equals("Vesuva")) {
|
||||||
if (!newOptions.isEmpty()) {
|
final String filter = logic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary" :
|
||||||
options = newOptions;
|
"Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
||||||
}
|
|
||||||
choice = ComputerUtilCard.getBestAI(options);
|
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||||
if (logic.equals("Vesuva") && "Vesuva".equals(choice.getName())) {
|
if (!newOptions.isEmpty()) {
|
||||||
choice = null;
|
options = newOptions;
|
||||||
}
|
}
|
||||||
} else if ("RandomNonLand".equals(logic)) {
|
choice = ComputerUtilCard.getBestAI(options);
|
||||||
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host);
|
if (logic.equals("Vesuva") && "Vesuva".equals(choice.getName())) {
|
||||||
choice = Aggregates.random(options);
|
choice = null;
|
||||||
} else if (logic.equals("Untap")) {
|
}
|
||||||
final String filter = "Permanent.YouCtrl,Permanent.tapped";
|
} else if ("RandomNonLand".equals(logic)) {
|
||||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host);
|
||||||
if (!newOptions.isEmpty()) {
|
choice = Aggregates.random(options);
|
||||||
options = newOptions;
|
} else if (logic.equals("Untap")) {
|
||||||
}
|
final String filter = "Permanent.YouCtrl,Permanent.tapped";
|
||||||
choice = ComputerUtilCard.getBestAI(options);
|
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||||
} else if (logic.equals("NeedsPrevention")) {
|
if (!newOptions.isEmpty()) {
|
||||||
final Game game = ai.getGame();
|
options = newOptions;
|
||||||
final Combat combat = game.getCombat();
|
}
|
||||||
CardCollectionView better = CardLists.filter(options, new Predicate<Card>() {
|
choice = ComputerUtilCard.getBestAI(options);
|
||||||
@Override
|
} else if (logic.equals("NeedsPrevention")) {
|
||||||
public boolean apply(final Card c) {
|
final Game game = ai.getGame();
|
||||||
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
final Combat combat = game.getCombat();
|
||||||
return false;
|
CardCollectionView better = CardLists.filter(options, new Predicate<Card>() {
|
||||||
}
|
@Override
|
||||||
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
|
public boolean apply(final Card c) {
|
||||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
|
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||||
}
|
return false;
|
||||||
});
|
}
|
||||||
if (!better.isEmpty()) {
|
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
|
||||||
choice = ComputerUtilCard.getBestAI(better);
|
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
|
||||||
} else {
|
}
|
||||||
choice = ComputerUtilCard.getBestAI(options);
|
});
|
||||||
}
|
if (!better.isEmpty()) {
|
||||||
} else if ("OppPreferred".equals(logic)) {
|
choice = ComputerUtilCard.getBestAI(better);
|
||||||
CardCollectionView oppControlled = CardLists.filterControlledBy(options, ai.getOpponents());
|
} else {
|
||||||
if (!oppControlled.isEmpty()) {
|
choice = ComputerUtilCard.getBestAI(options);
|
||||||
choice = ComputerUtilCard.getBestAI(oppControlled);
|
}
|
||||||
} else {
|
} else if ("OppPreferred".equals(logic)) {
|
||||||
CardCollectionView aiControlled = CardLists.filterControlledBy(options, ai);
|
CardCollectionView oppControlled = CardLists.filterControlledBy(options, ai.getOpponents());
|
||||||
choice = ComputerUtilCard.getWorstAI(aiControlled);
|
if (!oppControlled.isEmpty()) {
|
||||||
}
|
choice = ComputerUtilCard.getBestAI(oppControlled);
|
||||||
} else if ("LowestCMCCreature".equals(logic)) {
|
} else {
|
||||||
CardCollection creats = CardLists.filter(options, Presets.CREATURES);
|
CardCollectionView aiControlled = CardLists.filterControlledBy(options, ai);
|
||||||
creats = CardLists.filterToughness(creats, 1);
|
choice = ComputerUtilCard.getWorstAI(aiControlled);
|
||||||
if (creats.isEmpty()) {
|
}
|
||||||
choice = ComputerUtilCard.getWorstAI(options);
|
} else if ("LowestCMCCreature".equals(logic)) {
|
||||||
} else {
|
CardCollection creats = CardLists.filter(options, Presets.CREATURES);
|
||||||
CardLists.sortByCmcDesc(creats);
|
creats = CardLists.filterToughness(creats, 1);
|
||||||
Collections.reverse(creats);
|
if (creats.isEmpty()) {
|
||||||
choice = creats.get(0);
|
choice = ComputerUtilCard.getWorstAI(options);
|
||||||
}
|
} else {
|
||||||
} else if ("TangleWire".equals(logic)) {
|
CardLists.sortByCmcDesc(creats);
|
||||||
CardCollectionView betterList = CardLists.filter(options, new Predicate<Card>() {
|
Collections.reverse(creats);
|
||||||
@Override
|
choice = creats.get(0);
|
||||||
public boolean apply(final Card c) {
|
}
|
||||||
if (c.isCreature()) {
|
} else if ("NegativePowerFirst".equals(logic)) {
|
||||||
return false;
|
Card lowest = Aggregates.itemWithMin(options, CardPredicates.Accessors.fnGetNetPower);
|
||||||
}
|
if (lowest.getNetPower() <= 0) {
|
||||||
for (SpellAbility sa : c.getAllSpellAbilities()) {
|
choice = lowest;
|
||||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
} else {
|
||||||
return false;
|
choice = ComputerUtilCard.getBestCreatureAI(options);
|
||||||
}
|
}
|
||||||
}
|
} else if ("TangleWire".equals(logic)) {
|
||||||
return true;
|
CardCollectionView betterList = CardLists.filter(options, new Predicate<Card>() {
|
||||||
}
|
@Override
|
||||||
});
|
public boolean apply(final Card c) {
|
||||||
System.out.println("Tangle Wire" + options + " - " + betterList);
|
if (c.isCreature()) {
|
||||||
if (!betterList.isEmpty()) {
|
return false;
|
||||||
choice = betterList.get(0);
|
}
|
||||||
} else {
|
for (SpellAbility sa : c.getAllSpellAbilities()) {
|
||||||
choice = ComputerUtilCard.getWorstPermanentAI(options, false, false, false, false);
|
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
||||||
}
|
return false;
|
||||||
} else if (logic.equals("Duneblast")) {
|
}
|
||||||
CardCollectionView aiCreatures = ai.getCreaturesInPlay();
|
}
|
||||||
aiCreatures = CardLists.getNotKeyword(aiCreatures, "Indestructible");
|
return true;
|
||||||
|
}
|
||||||
if (aiCreatures.isEmpty()) {
|
});
|
||||||
return null;
|
System.out.println("Tangle Wire" + options + " - " + betterList);
|
||||||
}
|
if (!betterList.isEmpty()) {
|
||||||
|
choice = betterList.get(0);
|
||||||
Card chosen = ComputerUtilCard.getBestCreatureAI(aiCreatures);
|
} else {
|
||||||
return chosen;
|
choice = ComputerUtilCard.getWorstPermanentAI(options, false, false, false, false);
|
||||||
} else {
|
}
|
||||||
choice = ComputerUtilCard.getBestAI(options);
|
} else if (logic.equals("Duneblast")) {
|
||||||
}
|
CardCollectionView aiCreatures = ai.getCreaturesInPlay();
|
||||||
return choice;
|
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
|
||||||
}
|
|
||||||
}
|
if (aiCreatures.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Card chosen = ComputerUtilCard.getBestCreatureAI(aiCreatures);
|
||||||
|
return chosen;
|
||||||
|
} else if (logic.equals("OrzhovAdvokist")) {
|
||||||
|
if (ai.equals(sa.getActivatingPlayer())) {
|
||||||
|
choice = ComputerUtilCard.getBestAI(options);
|
||||||
|
} // TODO: improve ai
|
||||||
|
} else {
|
||||||
|
choice = ComputerUtilCard.getBestAI(options);
|
||||||
|
}
|
||||||
|
return choice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,122 +1,106 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
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.StaticData;
|
import forge.StaticData;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.card.CardDb;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.card.CardRules;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.card.CardSplitType;
|
||||||
import forge.card.CardDb;
|
import forge.card.CardStateName;
|
||||||
import forge.card.CardRules;
|
import forge.card.ICardFace;
|
||||||
import forge.card.CardSplitType;
|
import forge.game.card.Card;
|
||||||
import forge.card.CardStateName;
|
import forge.game.card.CardUtil;
|
||||||
import forge.card.ICardFace;
|
import forge.game.player.Player;
|
||||||
import forge.game.card.Card;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.card.CardUtil;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.item.PaperCard;
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
public class ChooseCardNameAi extends SpellAbilityAi {
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.item.PaperCard;
|
@Override
|
||||||
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
public class ChooseCardNameAi extends SpellAbilityAi {
|
Card source = sa.getHostCard();
|
||||||
|
if (sa.hasParam("AILogic")) {
|
||||||
@Override
|
// Don't tap creatures that may be able to block
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
if (ComputerUtil.waitForBlocking(sa)) {
|
||||||
Card source = sa.getHostCard();
|
return false;
|
||||||
if (sa.hasParam("AILogic")) {
|
}
|
||||||
// Don't tap creatures that may be able to block
|
|
||||||
if (ComputerUtil.waitForBlocking(sa)) {
|
String logic = sa.getParam("AILogic");
|
||||||
return false;
|
if (logic.equals("MomirAvatar")) {
|
||||||
}
|
return SpecialCardAi.MomirVigAvatar.consider(ai, sa);
|
||||||
|
} else if (logic.equals("CursedScroll")) {
|
||||||
String logic = sa.getParam("AILogic");
|
return SpecialCardAi.CursedScroll.consider(ai, sa);
|
||||||
if (logic.equals("MomirAvatar")) {
|
}
|
||||||
if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN1)) {
|
|
||||||
return false;
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
}
|
if (tgt != null) {
|
||||||
// Set PayX here to maximum value.
|
sa.resetTargets();
|
||||||
int tokenSize = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
if (tgt.canOnlyTgtOpponent()) {
|
||||||
|
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||||
// Some basic strategy for Momir
|
} else {
|
||||||
if (tokenSize < 2) {
|
sa.getTargets().add(ai);
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
if (tokenSize > 11) {
|
}
|
||||||
tokenSize = 11;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
source.setSVar("PayX", Integer.toString(tokenSize));
|
@Override
|
||||||
}
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
// TODO - there is no AILogic implemented yet
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
return false;
|
||||||
if (tgt != null) {
|
}
|
||||||
sa.resetTargets();
|
/* (non-Javadoc)
|
||||||
if (tgt.canOnlyTgtOpponent()) {
|
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||||
sa.getTargets().add(ai.getOpponent());
|
*/
|
||||||
} else {
|
@Override
|
||||||
sa.getTargets().add(ai);
|
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||||
}
|
|
||||||
}
|
return ComputerUtilCard.getBestAI(options);
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
return false;
|
@Override
|
||||||
}
|
public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) {
|
||||||
|
// this function is only for "Alhammarret, High Arbiter"
|
||||||
@Override
|
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
if (faces.isEmpty()) {
|
||||||
// TODO - there is no AILogic implemented yet
|
return "";
|
||||||
return false;
|
} else if (faces.size() == 1) {
|
||||||
}
|
return Iterables.getFirst(faces, null).getName();
|
||||||
/* (non-Javadoc)
|
}
|
||||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
|
||||||
*/
|
List<Card> cards = Lists.newArrayList();
|
||||||
@Override
|
final CardDb cardDb = StaticData.instance().getCommonCards();
|
||||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
|
||||||
return ComputerUtilCard.getBestAI(options);
|
for (ICardFace face : faces) {
|
||||||
}
|
final CardRules rules = cardDb.getRules(face.getName());
|
||||||
|
boolean isOther = rules.getOtherPart() == face;
|
||||||
@Override
|
final PaperCard paper = cardDb.getCard(rules.getName());
|
||||||
public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) {
|
final Card card = Card.fromPaperCard(paper, ai);
|
||||||
// this function is only for "Alhammarret, High Arbiter"
|
|
||||||
|
if (rules.getSplitType() == CardSplitType.Split) {
|
||||||
if (faces.isEmpty()) {
|
Card copy = CardUtil.getLKICopy(card);
|
||||||
return "";
|
// for calcing i need only one split side
|
||||||
} else if (faces.size() == 1) {
|
if (isOther) {
|
||||||
return Iterables.getFirst(faces, null).getName();
|
copy.getCurrentState().copyFrom(card.getState(CardStateName.RightSplit), true);
|
||||||
}
|
} else {
|
||||||
|
copy.getCurrentState().copyFrom(card.getState(CardStateName.LeftSplit), true);
|
||||||
List<Card> cards = Lists.newArrayList();
|
}
|
||||||
final CardDb cardDb = StaticData.instance().getCommonCards();
|
copy.updateStateForView();
|
||||||
|
|
||||||
for (ICardFace face : faces) {
|
cards.add(copy);
|
||||||
final CardRules rules = cardDb.getRules(face.getName());
|
} else if (!isOther) {
|
||||||
boolean isOther = rules.getOtherPart() == face;
|
// other can't be cast that way, not need to prevent that
|
||||||
final PaperCard paper = cardDb.getCard(rules.getName());
|
cards.add(card);
|
||||||
final Card card = Card.fromPaperCard(paper, ai);
|
}
|
||||||
|
}
|
||||||
if (rules.getSplitType() == CardSplitType.Split) {
|
|
||||||
Card copy = CardUtil.getLKICopy(card);
|
return ComputerUtilCard.getBestAI(cards).getName();
|
||||||
// for calcing i need only one split side
|
}
|
||||||
if (isOther) {
|
}
|
||||||
copy.getCurrentState().copyFrom(card, card.getState(CardStateName.RightSplit));
|
|
||||||
} else {
|
|
||||||
copy.getCurrentState().copyFrom(card, card.getState(CardStateName.LeftSplit));
|
|
||||||
}
|
|
||||||
copy.updateStateForView();
|
|
||||||
|
|
||||||
cards.add(copy);
|
|
||||||
} else if (!isOther) {
|
|
||||||
// other can't be cast that way, not need to prevent that
|
|
||||||
cards.add(card);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ComputerUtilCard.getBestAI(cards).getName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,98 +1,97 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicates;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.SpecialCardAi;
|
||||||
import forge.ai.SpecialCardAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.card.MagicColor;
|
||||||
import forge.card.MagicColor;
|
import forge.game.Game;
|
||||||
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.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.PhaseHandler;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.Player;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.util.MyRandom;
|
||||||
import forge.util.MyRandom;
|
|
||||||
|
public class ChooseColorAi extends SpellAbilityAi {
|
||||||
public class ChooseColorAi extends SpellAbilityAi {
|
|
||||||
|
@Override
|
||||||
@Override
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
final Card source = sa.getHostCard();
|
||||||
final Card source = sa.getHostCard();
|
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 false;
|
}
|
||||||
}
|
final String logic = sa.getParam("AILogic");
|
||||||
final String logic = sa.getParam("AILogic");
|
|
||||||
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
if ("Nykthos, Shrine to Nyx".equals(sourceName)) {
|
||||||
if ("Nykthos, Shrine to Nyx".equals(sourceName)) {
|
return SpecialCardAi.NykthosShrineToNyx.consider(ai, sa);
|
||||||
return SpecialCardAi.NykthosShrineToNyx.consider(ai, sa);
|
}
|
||||||
}
|
|
||||||
|
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 false;
|
}
|
||||||
}
|
// Set PayX here to maximum value.
|
||||||
// Set PayX here to maximum value.
|
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
source.setSVar("PayX", Integer.toString(x));
|
||||||
source.setSVar("PayX", Integer.toString(x));
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
if ("Addle".equals(sourceName)) {
|
||||||
if ("Addle".equals(sourceName)) {
|
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) || ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||||
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) || ai.getOpponent().getCardsIn(ZoneType.Hand).isEmpty()) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
if (logic.equals("MostExcessOpponentControls")) {
|
||||||
if (logic.equals("MostExcessOpponentControls")) {
|
for (byte color : MagicColor.WUBRG) {
|
||||||
for (byte color : MagicColor.WUBRG) {
|
CardCollectionView ailist = ai.getCardsIn(ZoneType.Battlefield);
|
||||||
CardCollectionView ailist = ai.getCardsIn(ZoneType.Battlefield);
|
CardCollectionView opplist = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
||||||
CardCollectionView opplist = ai.getOpponent().getCardsIn(ZoneType.Battlefield);
|
|
||||||
|
ailist = CardLists.filter(ailist, CardPredicates.isColor(color));
|
||||||
ailist = CardLists.filter(ailist, CardPredicates.isColor(color));
|
opplist = CardLists.filter(opplist, CardPredicates.isColor(color));
|
||||||
opplist = CardLists.filter(opplist, CardPredicates.isColor(color));
|
|
||||||
|
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 true;
|
}
|
||||||
}
|
}
|
||||||
}
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
if (logic.equals("MostProminentInComputerDeck")) {
|
||||||
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
|
// if there are some nonland permanents in hand
|
||||||
// if there are some nonland permanents in hand
|
CardCollectionView permanents = CardLists.filter(ai.getCardsIn(ZoneType.Hand),
|
||||||
CardCollectionView permanents = CardLists.filter(ai.getCardsIn(ZoneType.Hand),
|
CardPredicates.Presets.NONLAND_PERMANENTS);
|
||||||
CardPredicates.Presets.NONLAND_PERMANENTS);
|
|
||||||
|
return permanents.size() > 0 && ph.is(PhaseType.MAIN2, ai);
|
||||||
return permanents.size() > 0 && ph.is(PhaseType.MAIN2, ai);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
return chance;
|
||||||
return chance;
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
return mandatory || canPlayAI(ai, sa);
|
||||||
return mandatory || canPlayAI(ai, sa);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
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 ChooseDirectionAi extends SpellAbilityAi {
|
public class ChooseDirectionAi 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 boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final String logic = sa.getParam("AILogic");
|
final String logic = sa.getParam("AILogic");
|
||||||
if (logic == null) {
|
if (logic == null) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
// TODO: default ai
|
// TODO: default ai
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
return canPlayAI(ai, sa);
|
return canPlayAI(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
return canPlayAI(ai, sa);
|
return canPlayAI(ai, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,342 +1,350 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Iterables;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import com.google.common.collect.Lists;
|
||||||
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.SpellApiToAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.MagicColor;
|
import forge.ai.SpellApiToAi;
|
||||||
import forge.game.Game;
|
import forge.card.MagicColor;
|
||||||
import forge.game.card.Card;
|
import forge.game.Game;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CardUtil;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.player.Player;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.Aggregates;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.collect.FCollection;
|
import forge.util.Aggregates;
|
||||||
|
import forge.util.collect.FCollection;
|
||||||
|
|
||||||
public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|
||||||
|
public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||||
@Override
|
|
||||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
@Override
|
||||||
if ("Khans".equals(aiLogic) || "Dragons".equals(aiLogic)) {
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
return true;
|
if ("Khans".equals(aiLogic) || "Dragons".equals(aiLogic)) {
|
||||||
} else if (aiLogic.startsWith("Fabricate")) {
|
return true;
|
||||||
return true;
|
} else if (aiLogic.startsWith("Fabricate")) {
|
||||||
} else if ("Pump".equals(aiLogic) || "BestOption".equals(aiLogic)) {
|
return true;
|
||||||
for (AbilitySub sb : sa.getAdditionalAbilityList("Choices")) {
|
} else if ("Pump".equals(aiLogic) || "BestOption".equals(aiLogic)) {
|
||||||
if (SpellApiToAi.Converter.get(sb.getApi()).canPlayAIWithSubs(ai, sb)) {
|
for (AbilitySub sb : sa.getAdditionalAbilityList("Choices")) {
|
||||||
return true;
|
if (SpellApiToAi.Converter.get(sb.getApi()).canPlayAIWithSubs(ai, sb)) {
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
@Override
|
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
@Override
|
||||||
return sa.hasParam("AILogic");
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
}
|
return sa.hasParam("AILogic");
|
||||||
|
}
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
/* (non-Javadoc)
|
||||||
*/
|
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||||
@Override
|
*/
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
@Override
|
||||||
return canPlayAI(aiPlayer, sa);
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
}
|
return canPlayAI(aiPlayer, sa);
|
||||||
|
}
|
||||||
@Override
|
|
||||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
@Override
|
||||||
if ("CombustibleGearhulk".equals(sa.getParam("AILogic"))) {
|
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
for (final Player p : aiPlayer.getOpponents()) {
|
if ("CombustibleGearhulk".equals(sa.getParam("AILogic"))) {
|
||||||
if (p.canBeTargetedBy(sa)) {
|
for (final Player p : aiPlayer.getOpponents()) {
|
||||||
sa.resetTargets();
|
if (p.canBeTargetedBy(sa)) {
|
||||||
sa.getTargets().add(p);
|
sa.resetTargets();
|
||||||
return true;
|
sa.getTargets().add(p);
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
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 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 super.doTriggerAINoCost(aiPlayer, sa, mandatory);
|
|
||||||
}
|
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
|
||||||
|
}
|
||||||
@Override
|
|
||||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
@Override
|
||||||
Card host = sa.getHostCard();
|
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
Map<String, Object> params) {
|
||||||
final Game game = host.getGame();
|
Card host = sa.getHostCard();
|
||||||
final Combat combat = game.getCombat();
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
final String logic = sa.getParam("AILogic");
|
final Game game = host.getGame();
|
||||||
if (logic == null) {
|
final Combat combat = game.getCombat();
|
||||||
return spells.get(0);
|
final String logic = sa.getParam("AILogic");
|
||||||
} else if ("Random".equals(logic)) {
|
if (logic == null) {
|
||||||
return Aggregates.random(spells);
|
return spells.get(0);
|
||||||
} else if ("Phasing".equals(logic)) { // Teferi's Realm : keep aggressive
|
} else if ("Random".equals(logic)) {
|
||||||
List<SpellAbility> filtered = Lists.newArrayList(Iterables.filter(spells, new Predicate<SpellAbility>() {
|
return Aggregates.random(spells);
|
||||||
@Override
|
} else if ("Phasing".equals(logic)) { // Teferi's Realm : keep aggressive
|
||||||
public boolean apply(final SpellAbility sp) {
|
List<SpellAbility> filtered = Lists.newArrayList(Iterables.filter(spells, new Predicate<SpellAbility>() {
|
||||||
return !sp.getDescription().contains("Creature") && !sp.getDescription().contains("Land");
|
@Override
|
||||||
}
|
public boolean apply(final SpellAbility sp) {
|
||||||
}));
|
return !sp.getDescription().contains("Creature") && !sp.getDescription().contains("Land");
|
||||||
return Aggregates.random(filtered);
|
}
|
||||||
} else if ("PayUnlessCost".equals(logic)) {
|
}));
|
||||||
for (final SpellAbility sp : spells) {
|
return Aggregates.random(filtered);
|
||||||
String unlessCost = sp.getParam("UnlessCost");
|
} else if ("PayUnlessCost".equals(logic)) {
|
||||||
sp.setActivatingPlayer(sa.getActivatingPlayer());
|
for (final SpellAbility sp : spells) {
|
||||||
Cost unless = new Cost(unlessCost, false);
|
String unlessCost = sp.getParam("UnlessCost");
|
||||||
SpellAbility paycost = new SpellAbility.EmptySa(sa.getHostCard(), player);
|
sp.setActivatingPlayer(sa.getActivatingPlayer());
|
||||||
paycost.setPayCosts(unless);
|
Cost unless = new Cost(unlessCost, false);
|
||||||
if (ComputerUtilCost.willPayUnlessCost(sp, player, unless, false, new FCollection<Player>(player))
|
SpellAbility paycost = new SpellAbility.EmptySa(sa.getHostCard(), player);
|
||||||
&& ComputerUtilCost.canPayCost(paycost, player)) {
|
paycost.setPayCosts(unless);
|
||||||
return sp;
|
if (ComputerUtilCost.willPayUnlessCost(sp, player, unless, false, new FCollection<Player>(player))
|
||||||
}
|
&& ComputerUtilCost.canPayCost(paycost, player)) {
|
||||||
}
|
return sp;
|
||||||
return spells.get(0);
|
}
|
||||||
} else if ("Khans".equals(logic) || "Dragons".equals(logic)) { // Fate Reforged sieges
|
}
|
||||||
for (final SpellAbility sp : spells) {
|
return spells.get(0);
|
||||||
if (sp.getDescription().equals(logic)) {
|
} else if ("Khans".equals(logic) || "Dragons".equals(logic)) { // Fate Reforged sieges
|
||||||
return sp;
|
for (final SpellAbility sp : spells) {
|
||||||
}
|
if (sp.getDescription().equals(logic)) {
|
||||||
}
|
return sp;
|
||||||
} else if ("SelfOthers".equals(logic)) {
|
}
|
||||||
SpellAbility self = null, others = null;
|
}
|
||||||
for (final SpellAbility sp : spells) {
|
} else if ("SelfOthers".equals(logic)) {
|
||||||
if (sp.getDescription().equals("Self")) {
|
SpellAbility self = null, others = null;
|
||||||
self = sp;
|
for (final SpellAbility sp : spells) {
|
||||||
} else {
|
if (sp.getDescription().equals("Self")) {
|
||||||
others = sp;
|
self = sp;
|
||||||
}
|
} else {
|
||||||
}
|
others = sp;
|
||||||
String hostname = host.getName();
|
}
|
||||||
if (hostname.equals("May Civilization Collapse")) {
|
}
|
||||||
if (player.getLandsInPlay().isEmpty()) {
|
String hostname = host.getName();
|
||||||
return self;
|
if (hostname.equals("May Civilization Collapse")) {
|
||||||
}
|
if (player.getLandsInPlay().isEmpty()) {
|
||||||
} else if (hostname.equals("Feed the Machine")) {
|
return self;
|
||||||
if (player.getCreaturesInPlay().isEmpty()) {
|
}
|
||||||
return self;
|
} else if (hostname.equals("Feed the Machine")) {
|
||||||
}
|
if (player.getCreaturesInPlay().isEmpty()) {
|
||||||
} else if (hostname.equals("Surrender Your Thoughts")) {
|
return self;
|
||||||
if (player.getCardsIn(ZoneType.Hand).isEmpty()) {
|
}
|
||||||
return self;
|
} else if (hostname.equals("Surrender Your Thoughts")) {
|
||||||
}
|
if (player.getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||||
} else if (hostname.equals("The Fate of the Flammable")) {
|
return self;
|
||||||
if (!player.canLoseLife()) {
|
}
|
||||||
return self;
|
} else if (hostname.equals("The Fate of the Flammable")) {
|
||||||
}
|
if (!player.canLoseLife()) {
|
||||||
}
|
return self;
|
||||||
return others;
|
}
|
||||||
} else if ("Fatespinner".equals(logic)) {
|
}
|
||||||
SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null;
|
return others;
|
||||||
for (final SpellAbility sp : spells) {
|
} else if ("Fatespinner".equals(logic)) {
|
||||||
if (sp.getDescription().equals("FatespinnerSkipDraw")) {
|
SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null;
|
||||||
skipDraw = sp;
|
for (final SpellAbility sp : spells) {
|
||||||
} else if (sp.getDescription().equals("FatespinnerSkipMain")) {
|
if (sp.getDescription().equals("FatespinnerSkipDraw")) {
|
||||||
//skipMain = sp;
|
skipDraw = sp;
|
||||||
} else {
|
} else if (sp.getDescription().equals("FatespinnerSkipMain")) {
|
||||||
skipCombat = sp;
|
//skipMain = sp;
|
||||||
}
|
} else {
|
||||||
}
|
skipCombat = sp;
|
||||||
// FatespinnerSkipDraw,FatespinnerSkipMain,FatespinnerSkipCombat
|
}
|
||||||
if (player.hasKeyword("Skip your draw step.")) {
|
}
|
||||||
return skipDraw;
|
// FatespinnerSkipDraw,FatespinnerSkipMain,FatespinnerSkipCombat
|
||||||
}
|
if (player.hasKeyword("Skip your draw step.")) {
|
||||||
if (player.hasKeyword("Skip your next combat phase.")) {
|
return skipDraw;
|
||||||
return skipCombat;
|
}
|
||||||
}
|
if (player.hasKeyword("Skip your next combat phase.")) {
|
||||||
|
return skipCombat;
|
||||||
// TODO If combat is poor, Skip Combat
|
}
|
||||||
// Todo if hand is empty or mostly empty, skip main phase
|
|
||||||
// Todo if hand has gas, skip draw
|
// TODO If combat is poor, Skip Combat
|
||||||
return Aggregates.random(spells);
|
// Todo if hand is empty or mostly empty, skip main phase
|
||||||
|
// Todo if hand has gas, skip draw
|
||||||
} else if ("SinProdder".equals(logic)) {
|
return Aggregates.random(spells);
|
||||||
SpellAbility allow = null, deny = null;
|
|
||||||
for (final SpellAbility sp : spells) {
|
} else if ("SinProdder".equals(logic)) {
|
||||||
if (sp.getDescription().equals("Allow")) {
|
SpellAbility allow = null, deny = null;
|
||||||
allow = sp;
|
for (final SpellAbility sp : spells) {
|
||||||
} else {
|
if (sp.getDescription().equals("Allow")) {
|
||||||
deny = sp;
|
allow = sp;
|
||||||
}
|
} else {
|
||||||
}
|
deny = sp;
|
||||||
|
}
|
||||||
Card imprinted = host.getImprintedCards().getFirst();
|
}
|
||||||
int dmg = imprinted.getCMC();
|
|
||||||
Player owner = imprinted.getOwner();
|
Card imprinted = host.getImprintedCards().getFirst();
|
||||||
|
int dmg = imprinted.getCMC();
|
||||||
//useless cards in hand
|
Player owner = imprinted.getOwner();
|
||||||
if (imprinted.getName().equals("Bridge from Below") ||
|
|
||||||
imprinted.getName().equals("Haakon, Stromgald Scourge")) {
|
//useless cards in hand
|
||||||
return allow;
|
if (imprinted.getName().equals("Bridge from Below") ||
|
||||||
}
|
imprinted.getName().equals("Haakon, Stromgald Scourge")) {
|
||||||
|
return allow;
|
||||||
//bad cards when are thrown from the library to the graveyard, but Yixlid can prevent that
|
}
|
||||||
if (!player.getGame().isCardInPlay("Yixlid Jailer") && (
|
|
||||||
imprinted.getName().equals("Gaea's Blessing") ||
|
//bad cards when are thrown from the library to the graveyard, but Yixlid can prevent that
|
||||||
imprinted.getName().equals("Narcomoeba"))) {
|
if (!player.getGame().isCardInPlay("Yixlid Jailer") && (
|
||||||
return allow;
|
imprinted.getName().equals("Gaea's Blessing") ||
|
||||||
}
|
imprinted.getName().equals("Narcomoeba"))) {
|
||||||
|
return allow;
|
||||||
// milling against Tamiyo is pointless
|
}
|
||||||
if (owner.isCardInCommand("Tamiyo, the Moon Sage emblem")) {
|
|
||||||
return allow;
|
// milling against Tamiyo is pointless
|
||||||
}
|
if (owner.isCardInCommand("Emblem - Tamiyo, the Moon Sage")) {
|
||||||
|
return allow;
|
||||||
// milling a land against Gitrog result in card draw
|
}
|
||||||
if (imprinted.isLand() && owner.isCardInPlay("The Gitrog Monster")) {
|
|
||||||
// try to mill owner
|
// milling a land against Gitrog result in card draw
|
||||||
if (owner.getCardsIn(ZoneType.Library).size() < 5) {
|
if (imprinted.isLand() && owner.isCardInPlay("The Gitrog Monster")) {
|
||||||
return deny;
|
// try to mill owner
|
||||||
}
|
if (owner.getCardsIn(ZoneType.Library).size() < 5) {
|
||||||
return allow;
|
return deny;
|
||||||
}
|
}
|
||||||
|
return allow;
|
||||||
// milling a creature against Sidisi result in more creatures
|
}
|
||||||
if (imprinted.isCreature() && owner.isCardInPlay("Sidisi, Brood Tyrant")) {
|
|
||||||
return allow;
|
// milling a creature against Sidisi result in more creatures
|
||||||
}
|
if (imprinted.isCreature() && owner.isCardInPlay("Sidisi, Brood Tyrant")) {
|
||||||
|
return allow;
|
||||||
//if Iona does prevent from casting, allow it to draw
|
}
|
||||||
for (final Card io : player.getCardsIn(ZoneType.Battlefield, "Iona, Shield of Emeria")) {
|
|
||||||
if (CardUtil.getColors(imprinted).hasAnyColor(MagicColor.fromName(io.getChosenColor()))) {
|
//if Iona does prevent from casting, allow it to draw
|
||||||
return allow;
|
for (final Card io : player.getCardsIn(ZoneType.Battlefield, "Iona, Shield of Emeria")) {
|
||||||
}
|
if (CardUtil.getColors(imprinted).hasAnyColor(MagicColor.fromName(io.getChosenColor()))) {
|
||||||
}
|
return allow;
|
||||||
|
}
|
||||||
if (dmg == 0) {
|
}
|
||||||
// If CMC = 0, mill it!
|
|
||||||
return deny;
|
if (dmg == 0) {
|
||||||
} else if (dmg + 3 > player.getLife()) {
|
// If CMC = 0, mill it!
|
||||||
// if low on life, do nothing.
|
return deny;
|
||||||
return allow;
|
} else if (dmg + 3 > player.getLife()) {
|
||||||
} else if (player.getLife() - dmg > 15) {
|
// if low on life, do nothing.
|
||||||
// TODO Check "danger" level of card
|
return allow;
|
||||||
// If lots of life, and card might be dangerous? Mill it!
|
} else if (player.getLife() - dmg > 15) {
|
||||||
return deny;
|
// TODO Check "danger" level of card
|
||||||
}
|
// If lots of life, and card might be dangerous? Mill it!
|
||||||
// if unsure, random?
|
return deny;
|
||||||
return Aggregates.random(spells);
|
}
|
||||||
} else if (logic.startsWith("Fabricate")) {
|
// if unsure, random?
|
||||||
final int n = Integer.valueOf(logic.substring("Fabricate".length()));
|
return Aggregates.random(spells);
|
||||||
SpellAbility counterSA = spells.get(0), tokenSA = spells.get(1);
|
} else if (logic.startsWith("Fabricate")) {
|
||||||
|
final int n = Integer.valueOf(logic.substring("Fabricate".length()));
|
||||||
// check for something which might prevent the counters to be placed on host
|
if(spells.size() < 2) {
|
||||||
if (!host.canReceiveCounters(CounterType.P1P1)) {
|
// If the creature is no longer on the battlefield, the option
|
||||||
return tokenSA;
|
// to add counters is already removed at this point. Return the
|
||||||
}
|
// only available option: create servo tokens.
|
||||||
|
return spells.get(0);
|
||||||
// if host would leave the play or if host is useless, create tokens
|
}
|
||||||
if (host.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(player, host)) {
|
SpellAbility counterSA = spells.get(0), tokenSA = spells.get(1);
|
||||||
return tokenSA;
|
|
||||||
}
|
// check for something which might prevent the counters to be placed on host
|
||||||
|
if (!host.canReceiveCounters(CounterType.P1P1)) {
|
||||||
// need a copy for one with extra +1/+1 counter boost,
|
return tokenSA;
|
||||||
// without causing triggers to run
|
}
|
||||||
final Card copy = CardUtil.getLKICopy(host);
|
|
||||||
copy.setCounters(CounterType.P1P1, copy.getCounters(CounterType.P1P1) + n);
|
// if host would leave the play or if host is useless, create tokens
|
||||||
copy.setZone(host.getZone());
|
if (host.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(player, host)) {
|
||||||
|
return tokenSA;
|
||||||
// if host would put into the battlefield attacking
|
}
|
||||||
if (combat != null && combat.isAttacking(host)) {
|
|
||||||
final Player defender = combat.getDefenderPlayerByAttacker(host);
|
// need a copy for one with extra +1/+1 counter boost,
|
||||||
if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy)) {
|
// without causing triggers to run
|
||||||
return counterSA;
|
final Card copy = CardUtil.getLKICopy(host);
|
||||||
}
|
copy.setCounters(CounterType.P1P1, copy.getCounters(CounterType.P1P1) + n);
|
||||||
return tokenSA;
|
copy.setZone(host.getZone());
|
||||||
}
|
|
||||||
|
// if host would put into the battlefield attacking
|
||||||
// if the host has haste and can attack
|
if (combat != null && combat.isAttacking(host)) {
|
||||||
if (CombatUtil.canAttack(copy)) {
|
final Player defender = combat.getDefenderPlayerByAttacker(host);
|
||||||
for (final Player opp : player.getOpponents()) {
|
if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy)) {
|
||||||
if (CombatUtil.canAttack(copy, opp) &&
|
return counterSA;
|
||||||
opp.canLoseLife() &&
|
}
|
||||||
!ComputerUtilCard.canBeBlockedProfitably(opp, copy))
|
return tokenSA;
|
||||||
return counterSA;
|
}
|
||||||
}
|
|
||||||
}
|
// if the host has haste and can attack
|
||||||
|
if (CombatUtil.canAttack(copy)) {
|
||||||
// TODO check for trigger to turn token ETB into +1/+1 counter for host
|
for (final Player opp : player.getOpponents()) {
|
||||||
// TODO check for trigger to turn token ETB into damage or life loss for opponent
|
if (CombatUtil.canAttack(copy, opp) &&
|
||||||
// in this cases Token might be prefered even if they would not survive
|
opp.canLoseLife() &&
|
||||||
final Card tokenCard = TokenAi.spawnToken(player, tokenSA, true);
|
!ComputerUtilCard.canBeBlockedProfitably(opp, copy))
|
||||||
|
return counterSA;
|
||||||
// Token would not survive
|
}
|
||||||
if (tokenCard.getNetToughness() < 1) {
|
}
|
||||||
return counterSA;
|
|
||||||
}
|
// TODO check for trigger to turn token ETB into +1/+1 counter for host
|
||||||
|
// TODO check for trigger to turn token ETB into damage or life loss for opponent
|
||||||
// Special Card logic, this one try to median its power with the number of artifacts
|
// in this cases Token might be prefered even if they would not survive
|
||||||
if ("Marionette Master".equals(sourceName)) {
|
final Card tokenCard = TokenAi.spawnToken(player, tokenSA, true);
|
||||||
CardCollection list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), Presets.ARTIFACTS);
|
|
||||||
return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
|
// Token would not survive
|
||||||
} else if ("Cultivator of Blades".equals(sourceName)) {
|
if (tokenCard.getNetToughness() < 1) {
|
||||||
// Cultivator does try to median with number of Creatures
|
return counterSA;
|
||||||
CardCollection list = player.getCreaturesInPlay();
|
}
|
||||||
return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
|
|
||||||
}
|
// Special Card logic, this one try to median its power with the number of artifacts
|
||||||
|
if ("Marionette Master".equals(sourceName)) {
|
||||||
// evaluate Creature with +1/+1
|
CardCollection list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), Presets.ARTIFACTS);
|
||||||
int evalCounter = ComputerUtilCard.evaluateCreature(copy);
|
return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
|
||||||
|
} else if ("Cultivator of Blades".equals(sourceName)) {
|
||||||
final CardCollection tokenList = new CardCollection(host);
|
// Cultivator does try to median with number of Creatures
|
||||||
for (int i = 0; i < n; ++i) {
|
CardCollection list = player.getCreaturesInPlay();
|
||||||
tokenList.add(TokenAi.spawnToken(player, tokenSA));
|
return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluate Host with Tokens
|
// evaluate Creature with +1/+1
|
||||||
int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList);
|
int evalCounter = ComputerUtilCard.evaluateCreature(copy);
|
||||||
|
|
||||||
return evalToken >= evalCounter ? tokenSA : counterSA;
|
final CardCollection tokenList = new CardCollection(host);
|
||||||
} else if ("CombustibleGearhulk".equals(logic)) {
|
for (int i = 0; i < n; ++i) {
|
||||||
Player controller = sa.getActivatingPlayer();
|
tokenList.add(TokenAi.spawnToken(player, tokenSA));
|
||||||
List<ZoneType> zones = ZoneType.listValueOf("Graveyard, Battlefield, Exile");
|
}
|
||||||
int life = player.getLife();
|
|
||||||
CardCollectionView revealedCards = controller.getCardsIn(zones);
|
// evaluate Host with Tokens
|
||||||
|
int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList);
|
||||||
if (revealedCards.size() < 5) {
|
|
||||||
// Not very many revealed cards, just guess based on lifetotal
|
return evalToken >= evalCounter ? tokenSA : counterSA;
|
||||||
return life < 7 ? spells.get(0) : spells.get(1);
|
} else if ("CombustibleGearhulk".equals(logic)) {
|
||||||
}
|
Player controller = sa.getActivatingPlayer();
|
||||||
|
List<ZoneType> zones = ZoneType.listValueOf("Graveyard, Battlefield, Exile");
|
||||||
int totalCMC = 0;
|
int life = player.getLife();
|
||||||
for(Card c : revealedCards) {
|
CardCollectionView revealedCards = controller.getCardsIn(zones);
|
||||||
totalCMC += c.getCMC();
|
|
||||||
}
|
if (revealedCards.size() < 5) {
|
||||||
|
// Not very many revealed cards, just guess based on lifetotal
|
||||||
int bestGuessDamage = totalCMC * 3 / revealedCards.size();
|
return life < 7 ? spells.get(0) : spells.get(1);
|
||||||
return life <= bestGuessDamage ? spells.get(0) : spells.get(1);
|
}
|
||||||
} else if ("Pump".equals(logic) || "BestOption".equals(logic)) {
|
|
||||||
List<SpellAbility> filtered = Lists.newArrayList();
|
int totalCMC = 0;
|
||||||
// filter first for the spells which can be done
|
for(Card c : revealedCards) {
|
||||||
for (SpellAbility sp : spells) {
|
totalCMC += c.getCMC();
|
||||||
if (SpellApiToAi.Converter.get(sp.getApi()).canPlayAIWithSubs(player, sp)) {
|
}
|
||||||
filtered.add(sp);
|
|
||||||
}
|
int bestGuessDamage = totalCMC * 3 / revealedCards.size();
|
||||||
}
|
return life <= bestGuessDamage ? spells.get(0) : spells.get(1);
|
||||||
|
} else if ("Pump".equals(logic) || "BestOption".equals(logic)) {
|
||||||
// TODO find better way to check
|
List<SpellAbility> filtered = Lists.newArrayList();
|
||||||
if (!filtered.isEmpty()) {
|
// filter first for the spells which can be done
|
||||||
return filtered.get(0);
|
for (SpellAbility sp : spells) {
|
||||||
}
|
if (SpellApiToAi.Converter.get(sp.getApi()).canPlayAIWithSubs(player, sp)) {
|
||||||
}
|
filtered.add(sp);
|
||||||
return spells.get(0); // return first choice if no logic found
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO find better way to check
|
||||||
|
if (!filtered.isEmpty()) {
|
||||||
|
return filtered.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return spells.get(0); // return first choice if no logic found
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,34 +1,36 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.game.player.Player;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.MyRandom;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.util.MyRandom;
|
||||||
public class ChooseNumberAi extends SpellAbilityAi {
|
|
||||||
|
public class ChooseNumberAi extends SpellAbilityAi {
|
||||||
@Override
|
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
@Override
|
||||||
if (!sa.hasParam("AILogic")) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
return false;
|
if (!sa.hasParam("AILogic")) {
|
||||||
}
|
return false;
|
||||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
}
|
||||||
if (tgt != null) {
|
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
sa.resetTargets();
|
if (tgt != null) {
|
||||||
if (sa.canTarget(aiPlayer.getOpponent())) {
|
sa.resetTargets();
|
||||||
sa.getTargets().add(aiPlayer.getOpponent());
|
Player opp = ComputerUtil.getOpponentFor(aiPlayer);
|
||||||
} else {
|
if (sa.canTarget(opp)) {
|
||||||
return false;
|
sa.getTargets().add(opp);
|
||||||
}
|
} else {
|
||||||
}
|
return false;
|
||||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
}
|
||||||
return chance;
|
}
|
||||||
}
|
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
|
return chance;
|
||||||
@Override
|
}
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
|
||||||
return mandatory || canPlayAI(ai, sa);
|
@Override
|
||||||
}
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
return mandatory || canPlayAI(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,81 +1,81 @@
|
|||||||
package forge.ai.ability;
|
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.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
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.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ChoosePlayerAi extends SpellAbilityAi {
|
public class ChoosePlayerAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
return canPlayAI(ai, sa);
|
return canPlayAI(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
return canPlayAI(ai, sa);
|
return canPlayAI(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> choices) {
|
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> choices) {
|
||||||
Player chosen = null;
|
Player chosen = null;
|
||||||
if ("Curse".equals(sa.getParam("AILogic"))) {
|
if ("Curse".equals(sa.getParam("AILogic"))) {
|
||||||
for (Player pc : choices) {
|
for (Player pc : choices) {
|
||||||
if (pc.isOpponentOf(ai)) {
|
if (pc.isOpponentOf(ai)) {
|
||||||
chosen = pc;
|
chosen = pc;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (chosen == null) {
|
if (chosen == null) {
|
||||||
chosen = Iterables.getFirst(choices, null);
|
chosen = Iterables.getFirst(choices, null);
|
||||||
System.out.println("No good curse choices. Picking first available: " + chosen);
|
System.out.println("No good curse choices. Picking first available: " + chosen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ("Pump".equals(sa.getParam("AILogic"))) {
|
else if ("Pump".equals(sa.getParam("AILogic"))) {
|
||||||
chosen = Iterables.contains(choices, ai) ? ai : Iterables.getFirst(choices, null);
|
chosen = Iterables.contains(choices, ai) ? ai : Iterables.getFirst(choices, null);
|
||||||
}
|
}
|
||||||
else if ("BestAllyBoardPosition".equals(sa.getParam("AILogic"))) {
|
else if ("BestAllyBoardPosition".equals(sa.getParam("AILogic"))) {
|
||||||
List<Player> prefChoices = Lists.newArrayList(choices);
|
List<Player> prefChoices = Lists.newArrayList(choices);
|
||||||
prefChoices.removeAll(ai.getOpponents());
|
prefChoices.removeAll(ai.getOpponents());
|
||||||
if (!prefChoices.isEmpty()) {
|
if (!prefChoices.isEmpty()) {
|
||||||
chosen = ComputerUtil.evaluateBoardPosition(prefChoices);
|
chosen = ComputerUtil.evaluateBoardPosition(prefChoices);
|
||||||
}
|
}
|
||||||
if (chosen == null) {
|
if (chosen == null) {
|
||||||
chosen = Iterables.getFirst(choices, null);
|
chosen = Iterables.getFirst(choices, null);
|
||||||
System.out.println("No good curse choices. Picking first available: " + chosen);
|
System.out.println("No good curse choices. Picking first available: " + chosen);
|
||||||
}
|
}
|
||||||
} else if ("MostCardsInHand".equals(sa.getParam("AILogic"))) {
|
} else if ("MostCardsInHand".equals(sa.getParam("AILogic"))) {
|
||||||
int cardsInHand = 0;
|
int cardsInHand = 0;
|
||||||
for (final Player p : choices) {
|
for (final Player p : choices) {
|
||||||
int hand = p.getCardsIn(ZoneType.Hand).size();
|
int hand = p.getCardsIn(ZoneType.Hand).size();
|
||||||
if (hand >= cardsInHand) {
|
if (hand >= cardsInHand) {
|
||||||
chosen = p;
|
chosen = p;
|
||||||
cardsInHand = hand;
|
cardsInHand = hand;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if ("LeastCreatures".equals(sa.getParam("AILogic"))) {
|
} else if ("LeastCreatures".equals(sa.getParam("AILogic"))) {
|
||||||
int creats = 50;
|
int creats = 50;
|
||||||
for (final Player p : choices) {
|
for (final Player p : choices) {
|
||||||
int curr = p.getCreaturesInPlay().size();
|
int curr = p.getCreaturesInPlay().size();
|
||||||
if (curr <= creats) {
|
if (curr <= creats) {
|
||||||
chosen = p;
|
chosen = p;
|
||||||
creats = curr;
|
creats = curr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.out.println("Default player choice logic.");
|
System.out.println("Default player choice logic.");
|
||||||
chosen = Iterables.contains(choices, ai) ? ai : Iterables.getFirst(choices, null);
|
chosen = Iterables.contains(choices, ai) ? ai : Iterables.getFirst(choices, null);
|
||||||
}
|
}
|
||||||
return chosen;
|
return chosen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,194 +1,230 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.base.Predicates;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import forge.ai.ComputerUtilCard;
|
import com.google.common.collect.Lists;
|
||||||
import forge.ai.ComputerUtilCombat;
|
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.game.Game;
|
import forge.ai.ComputerUtilCombat;
|
||||||
import forge.game.GameObject;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.Game;
|
||||||
import forge.game.card.Card;
|
import forge.game.GameObject;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.card.Card;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.player.Player;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.spellability.SpellAbilityStackInstance;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
public class ChooseSourceAi extends SpellAbilityAi {
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
/* (non-Javadoc)
|
import forge.game.zone.ZoneType;
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
import forge.util.Aggregates;
|
||||||
*/
|
|
||||||
@Override
|
public class ChooseSourceAi extends SpellAbilityAi {
|
||||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
|
||||||
// TODO: AI Support! Currently this is copied from AF ChooseCard.
|
/* (non-Javadoc)
|
||||||
// When implementing AI, I believe AI also needs to be made aware of the damage sources chosen
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
// 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
|
@Override
|
||||||
// possible reason to attack with that creature).
|
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||||
final Card host = sa.getHostCard();
|
// TODO: AI Support! Currently this is copied from AF ChooseCard.
|
||||||
final Cost abCost = sa.getPayCosts();
|
// When implementing AI, I believe AI also needs to be made aware of the damage sources chosen
|
||||||
final Card source = sa.getHostCard();
|
// 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
|
||||||
if (abCost != null) {
|
// possible reason to attack with that creature).
|
||||||
// AI currently disabled for these costs
|
final Card host = sa.getHostCard();
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
final Cost abCost = sa.getPayCosts();
|
||||||
return false;
|
final Card source = sa.getHostCard();
|
||||||
}
|
|
||||||
|
if (abCost != null) {
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
// AI currently disabled for these costs
|
||||||
return false;
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
|
||||||
return false;
|
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
|
||||||
return false;
|
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||||
if (tgt != null) {
|
return false;
|
||||||
sa.resetTargets();
|
}
|
||||||
if (sa.canTarget(ai.getOpponent())) {
|
}
|
||||||
sa.getTargets().add(ai.getOpponent());
|
|
||||||
} else {
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
return false;
|
if (tgt != null) {
|
||||||
}
|
sa.resetTargets();
|
||||||
}
|
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.canTarget(opp)) {
|
||||||
final Game game = ai.getGame();
|
sa.getTargets().add(opp);
|
||||||
if (sa.getParam("AILogic").equals("NeedsPrevention")) {
|
} else {
|
||||||
if (!game.getStack().isEmpty()) {
|
return false;
|
||||||
final SpellAbility topStack = game.getStack().peekAbility();
|
}
|
||||||
if (sa.hasParam("Choices") && !topStack.getHostCard().isValid(sa.getParam("Choices"), ai, source, sa)) {
|
}
|
||||||
return false;
|
if (sa.hasParam("AILogic")) {
|
||||||
}
|
final Game game = ai.getGame();
|
||||||
final ApiType threatApi = topStack.getApi();
|
if (sa.getParam("AILogic").equals("NeedsPrevention")) {
|
||||||
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
|
if (!game.getStack().isEmpty()) {
|
||||||
return false;
|
final SpellAbility topStack = game.getStack().peekAbility();
|
||||||
}
|
if (sa.hasParam("Choices") && !topStack.getHostCard().isValid(sa.getParam("Choices"), ai, source, sa)) {
|
||||||
|
return false;
|
||||||
final Card threatSource = topStack.getHostCard();
|
}
|
||||||
List<? extends GameObject> objects = getTargets(topStack);
|
final ApiType threatApi = topStack.getApi();
|
||||||
if (!topStack.usesTargeting() && topStack.hasParam("ValidPlayers") && !topStack.hasParam("Defined")) {
|
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
|
||||||
objects = AbilityUtils.getDefinedPlayers(threatSource, topStack.getParam("ValidPlayers"), topStack);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) {
|
final Card threatSource = topStack.getHostCard();
|
||||||
return false;
|
List<? extends GameObject> objects = getTargets(topStack);
|
||||||
}
|
if (!topStack.usesTargeting() && topStack.hasParam("ValidPlayers") && !topStack.hasParam("Defined")) {
|
||||||
int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
|
objects = AbilityUtils.getDefinedPlayers(threatSource, topStack.getParam("ValidPlayers"), topStack);
|
||||||
if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) {
|
}
|
||||||
return false;
|
|
||||||
}
|
if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
|
int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
|
||||||
return false;
|
if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) {
|
||||||
}
|
return false;
|
||||||
CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
|
}
|
||||||
if (sa.hasParam("Choices")) {
|
return true;
|
||||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
}
|
||||||
}
|
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
|
||||||
final Combat combat = game.getCombat();
|
return false;
|
||||||
choices = CardLists.filter(choices, new Predicate<Card>() {
|
}
|
||||||
@Override
|
CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
|
||||||
public boolean apply(final Card c) {
|
if (sa.hasParam("Choices")) {
|
||||||
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||||
return false;
|
}
|
||||||
}
|
final Combat combat = game.getCombat();
|
||||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
|
choices = CardLists.filter(choices, new Predicate<Card>() {
|
||||||
}
|
@Override
|
||||||
});
|
public boolean apply(final Card c) {
|
||||||
if (choices.isEmpty()) {
|
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
return true;
|
if (choices.isEmpty()) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@Override
|
|
||||||
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
return true;
|
||||||
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
|
}
|
||||||
final Player ai = sa.getActivatingPlayer();
|
|
||||||
final Game game = ai.getGame();
|
|
||||||
if (!game.getStack().isEmpty()) {
|
|
||||||
Card choseCard = chooseCardOnStack(sa, ai, game);
|
@Override
|
||||||
if (choseCard != null) {
|
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||||
return choseCard;
|
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
|
||||||
}
|
final Player ai = sa.getActivatingPlayer();
|
||||||
}
|
final Game game = ai.getGame();
|
||||||
|
if (!game.getStack().isEmpty()) {
|
||||||
final Combat combat = game.getCombat();
|
Card chosenCard = chooseCardOnStack(sa, ai, game);
|
||||||
|
if (chosenCard != null) {
|
||||||
List<Card> permanentSources = CardLists.filter(options, new Predicate<Card>() {
|
return chosenCard;
|
||||||
@Override
|
}
|
||||||
public boolean apply(final Card c) {
|
}
|
||||||
if (c == null || c.getZone() == null || c.getZone().getZoneType() != ZoneType.Battlefield
|
|
||||||
|| combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
final Combat combat = game.getCombat();
|
||||||
return false;
|
|
||||||
}
|
List<Card> permanentSources = CardLists.filter(options, new Predicate<Card>() {
|
||||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
|
@Override
|
||||||
}
|
public boolean apply(final Card c) {
|
||||||
});
|
if (c == null || c.getZone() == null || c.getZone().getZoneType() != ZoneType.Battlefield
|
||||||
return ComputerUtilCard.getBestCreatureAI(permanentSources);
|
|| combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||||
|
return false;
|
||||||
} else {
|
}
|
||||||
return ComputerUtilCard.getBestAI(options);
|
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
private Card chooseCardOnStack(SpellAbility sa, Player ai, Game game) {
|
// Try to choose the best creature for damage prevention.
|
||||||
for (SpellAbilityStackInstance si : game.getStack()) {
|
Card bestCreature = ComputerUtilCard.getBestCreatureAI(permanentSources);
|
||||||
final Card source = si.getSourceCard();
|
if (bestCreature != null) {
|
||||||
final SpellAbility abilityOnStack = si.getSpellAbility(true);
|
return bestCreature;
|
||||||
|
} else {
|
||||||
if (sa.hasParam("Choices") && !abilityOnStack.getHostCard().isValid(sa.getParam("Choices"), ai, sa.getHostCard(), sa)) {
|
// No optimal creature was found above, so try to broaden the choice.
|
||||||
continue;
|
if (!Iterables.isEmpty(options)) {
|
||||||
}
|
List<Card> oppCreatures = CardLists.filter(options, Predicates.and(CardPredicates.Presets.CREATURES,
|
||||||
final ApiType threatApi = abilityOnStack.getApi();
|
Predicates.not(CardPredicates.isOwner(aiChoser))));
|
||||||
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
|
List<Card> aiNonCreatures = CardLists.filter(options, Predicates.and(Predicates.not(CardPredicates.Presets.CREATURES),
|
||||||
continue;
|
CardPredicates.Presets.PERMANENTS, CardPredicates.isOwner(aiChoser)));
|
||||||
}
|
|
||||||
|
if (!oppCreatures.isEmpty()) {
|
||||||
List<? extends GameObject> objects = getTargets(abilityOnStack);
|
return ComputerUtilCard.getBestCreatureAI(oppCreatures);
|
||||||
|
} else if (!aiNonCreatures.isEmpty()) {
|
||||||
if (!abilityOnStack.usesTargeting() && !abilityOnStack.hasParam("Defined") && abilityOnStack.hasParam("ValidPlayers"))
|
return Aggregates.random(aiNonCreatures);
|
||||||
objects = AbilityUtils.getDefinedPlayers(source, abilityOnStack.getParam("ValidPlayers"), abilityOnStack);
|
} else {
|
||||||
|
return Aggregates.random(options);
|
||||||
if (!objects.contains(ai) || abilityOnStack.hasParam("NoPrevention")) {
|
}
|
||||||
continue;
|
} else if (!game.getStack().isEmpty()) {
|
||||||
}
|
// No permanent for the AI to choose. Should normally not happen unless using dev mode or something,
|
||||||
int dmg = AbilityUtils.calculateAmount(source, abilityOnStack.getParam("NumDmg"), abilityOnStack);
|
// but when it does happen, choose the top card on stack if possible (generally it'll be the SA
|
||||||
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false) <= 0) {
|
// source) in order to choose at least something, or the game will hang.
|
||||||
continue;
|
return game.getStack().peekAbility().getHostCard();
|
||||||
}
|
}
|
||||||
return source;
|
}
|
||||||
}
|
|
||||||
return null;
|
// Should never get here
|
||||||
}
|
System.err.println("Unexpected behavior: The AI was unable to choose anything for AF ChooseSource in "
|
||||||
|
+ sa.getHostCard() + ", the game will likely hang.");
|
||||||
private static List<GameObject> getTargets(final SpellAbility sa) {
|
return null;
|
||||||
return sa.usesTargeting() && (!sa.hasParam("Defined"))
|
} else {
|
||||||
? Lists.newArrayList(sa.getTargets().getTargets())
|
return ComputerUtilCard.getBestAI(options);
|
||||||
: AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private Card chooseCardOnStack(SpellAbility sa, Player ai, Game game) {
|
||||||
|
for (SpellAbilityStackInstance si : game.getStack()) {
|
||||||
|
final Card source = si.getSourceCard();
|
||||||
|
final SpellAbility abilityOnStack = si.getSpellAbility(true);
|
||||||
|
|
||||||
|
if (sa.hasParam("Choices") && !abilityOnStack.getHostCard().isValid(sa.getParam("Choices"), ai, sa.getHostCard(), sa)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final ApiType threatApi = abilityOnStack.getApi();
|
||||||
|
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<? extends GameObject> objects = getTargets(abilityOnStack);
|
||||||
|
|
||||||
|
if (!abilityOnStack.usesTargeting() && !abilityOnStack.hasParam("Defined") && abilityOnStack.hasParam("ValidPlayers"))
|
||||||
|
objects = AbilityUtils.getDefinedPlayers(source, abilityOnStack.getParam("ValidPlayers"), abilityOnStack);
|
||||||
|
|
||||||
|
if (!objects.contains(ai) || abilityOnStack.hasParam("NoPrevention")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int dmg = AbilityUtils.calculateAmount(source, abilityOnStack.getParam("NumDmg"), abilityOnStack);
|
||||||
|
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false) <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<GameObject> getTargets(final SpellAbility sa) {
|
||||||
|
return sa.usesTargeting() && (!sa.hasParam("Defined"))
|
||||||
|
? Lists.newArrayList(sa.getTargets().getTargets())
|
||||||
|
: AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,130 +1,130 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import forge.ai.AiCardMemory;
|
import forge.ai.AiCardMemory;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ChooseTypeAi extends SpellAbilityAi {
|
public class ChooseTypeAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
if (!sa.hasParam("AILogic")) {
|
if (!sa.hasParam("AILogic")) {
|
||||||
return false;
|
return false;
|
||||||
} else if ("MostProminentComputerControls".equals(sa.getParam("AILogic"))) {
|
} else if ("MostProminentComputerControls".equals(sa.getParam("AILogic"))) {
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
|
||||||
return doMirrorEntityLogic(aiPlayer, sa);
|
return doMirrorEntityLogic(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return doTriggerAINoCost(aiPlayer, sa, false);
|
return doTriggerAINoCost(aiPlayer, sa, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doMirrorEntityLogic(Player aiPlayer, SpellAbility sa) {
|
private boolean doMirrorEntityLogic(Player aiPlayer, SpellAbility sa) {
|
||||||
if (AiCardMemory.isRememberedCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN)) {
|
if (AiCardMemory.isRememberedCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1, aiPlayer)) {
|
if (!aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1, aiPlayer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollectionView otb = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
CardCollectionView otb = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||||
List<String> valid = Lists.newArrayList(CardType.getAllCreatureTypes());
|
List<String> valid = Lists.newArrayList(CardType.getAllCreatureTypes());
|
||||||
|
|
||||||
String chosenType = ComputerUtilCard.getMostProminentType(otb, valid);
|
String chosenType = ComputerUtilCard.getMostProminentType(otb, valid);
|
||||||
if (chosenType.isEmpty()) {
|
if (chosenType.isEmpty()) {
|
||||||
// Account for the situation when only changelings are on the battlefield
|
// Account for the situation when only changelings are on the battlefield
|
||||||
boolean allChangeling = false;
|
boolean allChangeling = false;
|
||||||
for (Card c : otb) {
|
for (Card c : otb) {
|
||||||
if (c.isCreature() && c.hasStartOfKeyword(Keyword.CHANGELING.toString())) {
|
if (c.isCreature() && c.hasStartOfKeyword(Keyword.CHANGELING.toString())) {
|
||||||
chosenType = Aggregates.random(valid); // just choose a random type for changelings
|
chosenType = Aggregates.random(valid); // just choose a random type for changelings
|
||||||
allChangeling = true;
|
allChangeling = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!allChangeling) {
|
if (!allChangeling) {
|
||||||
// Still empty, probably no creatures on board
|
// Still empty, probably no creatures on board
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int maxX = ComputerUtilMana.determineMaxAffordableX(aiPlayer, sa);
|
int maxX = ComputerUtilMana.determineMaxAffordableX(aiPlayer, sa);
|
||||||
int avgPower = 0;
|
int avgPower = 0;
|
||||||
|
|
||||||
// predict the opposition
|
// predict the opposition
|
||||||
CardCollection oppCreatures = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), CardPredicates.Presets.UNTAPPED);
|
CardCollection oppCreatures = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), CardPredicates.Presets.UNTAPPED);
|
||||||
int maxOppPower = 0;
|
int maxOppPower = 0;
|
||||||
int maxOppToughness = 0;
|
int maxOppToughness = 0;
|
||||||
int oppUsefulCreatures = 0;
|
int oppUsefulCreatures = 0;
|
||||||
|
|
||||||
for (Card oppCre : oppCreatures) {
|
for (Card oppCre : oppCreatures) {
|
||||||
if (ComputerUtilCard.isUselessCreature(aiPlayer, oppCre)) {
|
if (ComputerUtilCard.isUselessCreature(aiPlayer, oppCre)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (oppCre.getNetPower() > maxOppPower) {
|
if (oppCre.getNetPower() > maxOppPower) {
|
||||||
maxOppPower = oppCre.getNetPower();
|
maxOppPower = oppCre.getNetPower();
|
||||||
}
|
}
|
||||||
if (oppCre.getNetToughness() > maxOppToughness) {
|
if (oppCre.getNetToughness() > maxOppToughness) {
|
||||||
maxOppToughness = oppCre.getNetToughness();
|
maxOppToughness = oppCre.getNetToughness();
|
||||||
}
|
}
|
||||||
oppUsefulCreatures++;
|
oppUsefulCreatures++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxX > 1) {
|
if (maxX > 1) {
|
||||||
CardCollection cre = CardLists.filter(aiPlayer.getCardsIn(ZoneType.Battlefield),
|
CardCollection cre = CardLists.filter(aiPlayer.getCardsIn(ZoneType.Battlefield),
|
||||||
Predicates.and(CardPredicates.isType(chosenType), CardPredicates.Presets.UNTAPPED));
|
Predicates.and(CardPredicates.isType(chosenType), CardPredicates.Presets.UNTAPPED));
|
||||||
if (!cre.isEmpty()) {
|
if (!cre.isEmpty()) {
|
||||||
for (Card c: cre) {
|
for (Card c: cre) {
|
||||||
avgPower += c.getNetPower();
|
avgPower += c.getNetPower();
|
||||||
}
|
}
|
||||||
avgPower /= cre.size();
|
avgPower /= cre.size();
|
||||||
|
|
||||||
boolean overpower = cre.size() > oppUsefulCreatures;
|
boolean overpower = cre.size() > oppUsefulCreatures;
|
||||||
if (!overpower) {
|
if (!overpower) {
|
||||||
maxX = Math.max(0, maxX - 3); // conserve some mana unless the board position looks overpowering
|
maxX = Math.max(0, maxX - 3); // conserve some mana unless the board position looks overpowering
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxX > avgPower && maxX > maxOppPower && maxX >= maxOppToughness) {
|
if (maxX > avgPower && maxX > maxOppPower && maxX >= maxOppToughness) {
|
||||||
sa.setSVar("PayX", String.valueOf(maxX));
|
sa.setSVar("PayX", String.valueOf(maxX));
|
||||||
AiCardMemory.rememberCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN);
|
AiCardMemory.rememberCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
} 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) {
|
if (p.isOpponentOf(ai) && !mandatory) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,110 +1,110 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
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;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerCollection;
|
import forge.game.player.PlayerCollection;
|
||||||
import forge.game.player.PlayerPredicates;
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class ClashAi extends SpellAbilityAi {
|
public class ClashAi extends SpellAbilityAi {
|
||||||
|
|
||||||
/* (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 aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||||
* forge.game.spellability.SpellAbility)
|
* forge.game.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected boolean 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
return legalAction;
|
return legalAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see forge.ai.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player,
|
* @see forge.ai.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player,
|
||||||
* forge.game.spellability.SpellAbility, java.lang.Iterable)
|
* forge.game.spellability.SpellAbility, java.lang.Iterable)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||||
for (Player p : options) {
|
for (Player p : options) {
|
||||||
if (p.getCardsIn(ZoneType.Library).size() == 0)
|
if (p.getCardsIn(ZoneType.Library).size() == 0)
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollectionView col = ai.getCardsIn(ZoneType.Library);
|
CardCollectionView col = ai.getCardsIn(ZoneType.Library);
|
||||||
if (!col.isEmpty() && col.getFirst().mayPlayerLook(ai)) {
|
if (!col.isEmpty() && col.getFirst().mayPlayerLook(ai)) {
|
||||||
final Card top = col.get(0);
|
final Card top = col.get(0);
|
||||||
for (Player p : options) {
|
for (Player p : options) {
|
||||||
final Card oppTop = p.getCardsIn(ZoneType.Library).getFirst();
|
final Card oppTop = p.getCardsIn(ZoneType.Library).getFirst();
|
||||||
// TODO add logic for SplitCards
|
// TODO add logic for SplitCards
|
||||||
if (top.getCMC() > oppTop.getCMC()) {
|
if (top.getCMC() > oppTop.getCMC()) {
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Iterables.getFirst(options, null);
|
return Iterables.getFirst(options, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean selectTarget(Player ai, SpellAbility sa) {
|
private boolean selectTarget(Player ai, SpellAbility sa) {
|
||||||
String valid = sa.getParam("ValidTgts");
|
String valid = sa.getParam("ValidTgts");
|
||||||
|
|
||||||
PlayerCollection players = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
PlayerCollection players = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
// use chooseSinglePlayer function to the select player
|
// use chooseSinglePlayer function to the select player
|
||||||
Player chosen = chooseSinglePlayer(ai, sa, players);
|
Player chosen = chooseSinglePlayer(ai, sa, players);
|
||||||
if (chosen != null) {
|
if (chosen != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(chosen);
|
sa.getTargets().add(chosen);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("Creature".equals(valid)) {
|
if ("Creature".equals(valid)) {
|
||||||
// Springjack Knight
|
// Springjack Knight
|
||||||
// TODO: Whirlpool Whelm also uses creature targeting but it's trickier to support
|
// TODO: Whirlpool Whelm also uses creature targeting but it's trickier to support
|
||||||
CardCollectionView aiCreats = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
CardCollectionView aiCreats = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
||||||
CardCollectionView oppCreats = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
CardCollectionView oppCreats = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
||||||
|
|
||||||
Card tgt = aiCreats.isEmpty() ? ComputerUtilCard.getWorstCreatureAI(oppCreats) : ComputerUtilCard.getBestCreatureAI(aiCreats);
|
Card tgt = aiCreats.isEmpty() ? ComputerUtilCard.getWorstCreatureAI(oppCreats) : ComputerUtilCard.getBestCreatureAI(aiCreats);
|
||||||
|
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(tgt);
|
sa.getTargets().add(tgt);
|
||||||
} else {
|
} else {
|
||||||
return false; // cut short if this part of the clause is not satisfiable (with current card pool should never get here)
|
return false; // cut short if this part of the clause is not satisfiable (with current card pool should never get here)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.getTargets().getNumTargeted() > 0;
|
return sa.getTargets().getNumTargeted() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,188 +1,195 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.game.Game;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.Game;
|
import forge.game.card.Card;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.player.Player;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.player.Player;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
|
import java.util.List;
|
||||||
public class CloneAi extends SpellAbilityAi {
|
|
||||||
|
public class CloneAi extends SpellAbilityAi {
|
||||||
@Override
|
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
@Override
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Game game = source.getGame();
|
final Card source = sa.getHostCard();
|
||||||
|
final Game game = source.getGame();
|
||||||
boolean useAbility = true;
|
|
||||||
|
boolean useAbility = true;
|
||||||
// if (card.getController().isComputer()) {
|
|
||||||
// final List<Card> creatures = AllZoneUtil.getCreaturesInPlay();
|
// if (card.getController().isComputer()) {
|
||||||
// if (!creatures.isEmpty()) {
|
// final List<Card> creatures = AllZoneUtil.getCreaturesInPlay();
|
||||||
// cardToCopy = CardFactoryUtil.getBestCreatureAI(creatures);
|
// if (!creatures.isEmpty()) {
|
||||||
// }
|
// cardToCopy = CardFactoryUtil.getBestCreatureAI(creatures);
|
||||||
// }
|
// }
|
||||||
|
// }
|
||||||
// TODO - add some kind of check to answer
|
|
||||||
// "Am I going to attack with this?"
|
// TODO - add some kind of check to answer
|
||||||
// TODO - add some kind of check for during human turn to answer
|
// "Am I going to attack with this?"
|
||||||
// "Can I use this to block something?"
|
// TODO - add some kind of check for during human turn to answer
|
||||||
|
// "Can I use this to block something?"
|
||||||
PhaseHandler phase = game.getPhaseHandler();
|
|
||||||
// don't use instant speed clone abilities outside computers
|
PhaseHandler phase = game.getPhaseHandler();
|
||||||
// Combat_Begin step
|
// don't use instant speed clone abilities outside computers
|
||||||
if (!phase.is(PhaseType.COMBAT_BEGIN)
|
// Combat_Begin step
|
||||||
&& phase.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
if (!phase.is(PhaseType.COMBAT_BEGIN)
|
||||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
|
&& phase.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
||||||
return false;
|
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
// don't use instant speed clone abilities outside humans
|
|
||||||
// Combat_Declare_Attackers_InstantAbility step
|
// don't use instant speed clone abilities outside humans
|
||||||
if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || phase.isPlayerTurn(ai) || game.getCombat().getAttackers().isEmpty()) {
|
// Combat_Declare_Attackers_InstantAbility step
|
||||||
return false;
|
if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || phase.isPlayerTurn(ai) || game.getCombat().getAttackers().isEmpty()) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
// don't activate during main2 unless this effect is permanent
|
|
||||||
if (phase.is(PhaseType.MAIN2) && !sa.hasParam("Permanent")) {
|
// don't activate during main2 unless this effect is permanent
|
||||||
return false;
|
if (phase.is(PhaseType.MAIN2) && !sa.hasParam("Permanent")) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
if (null == tgt) {
|
|
||||||
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
if (null == tgt) {
|
||||||
|
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
boolean bFlag = false;
|
|
||||||
for (final Card c : defined) {
|
boolean bFlag = false;
|
||||||
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));
|
for (final Card c : defined) {
|
||||||
|
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));
|
||||||
// for creatures that could be improved (like Figure of Destiny)
|
|
||||||
if (c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
|
// for creatures that could be improved (like Figure of Destiny)
|
||||||
int power = -5;
|
if (c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
|
||||||
if (sa.hasParam("Power")) {
|
int power = -5;
|
||||||
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
|
if (sa.hasParam("Power")) {
|
||||||
}
|
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
|
||||||
int toughness = -5;
|
}
|
||||||
if (sa.hasParam("Toughness")) {
|
int toughness = -5;
|
||||||
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
|
if (sa.hasParam("Toughness")) {
|
||||||
}
|
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
|
||||||
if ((power + toughness) > (c.getCurrentPower() + c.getCurrentToughness())) {
|
}
|
||||||
bFlag = true;
|
if ((power + toughness) > (c.getCurrentPower() + c.getCurrentToughness())) {
|
||||||
}
|
bFlag = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
if (!bFlag) { // All of the defined stuff is cloned, not very
|
|
||||||
// useful
|
if (!bFlag) { // All of the defined stuff is cloned, not very
|
||||||
return false;
|
// useful
|
||||||
}
|
return false;
|
||||||
} else {
|
}
|
||||||
sa.resetTargets();
|
} else {
|
||||||
useAbility &= cloneTgtAI(sa);
|
sa.resetTargets();
|
||||||
}
|
useAbility &= cloneTgtAI(sa);
|
||||||
|
}
|
||||||
return useAbility;
|
|
||||||
} // end cloneCanPlayAI()
|
return useAbility;
|
||||||
|
} // end cloneCanPlayAI()
|
||||||
@Override
|
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
@Override
|
||||||
// AI should only activate this during Human's turn
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
boolean chance = true;
|
// AI should only activate this during Human's turn
|
||||||
|
boolean chance = true;
|
||||||
if (sa.usesTargeting()) {
|
|
||||||
chance = cloneTgtAI(sa);
|
if (sa.usesTargeting()) {
|
||||||
}
|
chance = cloneTgtAI(sa);
|
||||||
|
}
|
||||||
|
|
||||||
return chance;
|
|
||||||
}
|
return chance;
|
||||||
|
}
|
||||||
@Override
|
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
boolean chance = true;
|
|
||||||
|
boolean chance = true;
|
||||||
if (sa.usesTargeting()) {
|
|
||||||
chance = cloneTgtAI(sa);
|
if (sa.usesTargeting()) {
|
||||||
}
|
chance = cloneTgtAI(sa);
|
||||||
|
}
|
||||||
// Improve AI for triggers. If source is a creature with:
|
|
||||||
// When ETB, sacrifice a creature. Check to see if the AI has something
|
// Improve AI for triggers. If source is a creature with:
|
||||||
// to sacrifice
|
// When ETB, sacrifice a creature. Check to see if the AI has something
|
||||||
|
// to sacrifice
|
||||||
// Eventually, we can call the trigger of ETB abilities with
|
|
||||||
// not mandatory as part of the checks to cast something
|
// Eventually, we can call the trigger of ETB abilities with
|
||||||
|
// not mandatory as part of the checks to cast something
|
||||||
return chance || mandatory;
|
|
||||||
}
|
return chance || mandatory;
|
||||||
|
}
|
||||||
/**
|
|
||||||
* <p>
|
/**
|
||||||
* cloneTgtAI.
|
* <p>
|
||||||
* </p>
|
* cloneTgtAI.
|
||||||
*
|
* </p>
|
||||||
* @param af
|
*
|
||||||
* a {@link forge.game.ability.AbilityFactory} object.
|
* @param sa
|
||||||
* @param sa
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* @return a boolean.
|
||||||
* @return a boolean.
|
*/
|
||||||
*/
|
private boolean cloneTgtAI(final SpellAbility sa) {
|
||||||
private boolean cloneTgtAI(final SpellAbility sa) {
|
// Specific logic for cards
|
||||||
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
|
if ("CloneAttacker".equals(sa.getParam("AILogic"))) {
|
||||||
// two are the only things
|
CardCollection valid = CardLists.getValidCards(sa.getHostCard().getController().getCardsIn(ZoneType.Battlefield), sa.getParam("ValidTgts"), sa.getHostCard().getController(), sa.getHostCard());
|
||||||
// that clone a target. Those can just use SVar:RemAIDeck:True until
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(valid));
|
||||||
// this can do a reasonably
|
return true;
|
||||||
// good job of picking a good target
|
}
|
||||||
return false;
|
|
||||||
}
|
// Default:
|
||||||
|
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
|
||||||
/* (non-Javadoc)
|
// two are the only things
|
||||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
// that clone a target. Those can just use SVar:RemAIDeck:True until
|
||||||
*/
|
// this can do a reasonably
|
||||||
@Override
|
// good job of picking a good target
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
return false;
|
||||||
// Didn't confirm in the original code
|
}
|
||||||
return false;
|
|
||||||
}
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||||
/*
|
*/
|
||||||
* (non-Javadoc)
|
@Override
|
||||||
*
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
|
// Didn't confirm in the original code
|
||||||
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
|
return false;
|
||||||
* forge.game.player.Player)
|
}
|
||||||
*/
|
|
||||||
@Override
|
/*
|
||||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
* (non-Javadoc)
|
||||||
Player targetedPlayer) {
|
*
|
||||||
final Card host = sa.getHostCard();
|
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
|
||||||
final Player ctrl = host.getController();
|
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
|
||||||
|
* forge.game.player.Player)
|
||||||
final boolean isVesuva = "Vesuva".equals(host.getName());
|
*/
|
||||||
|
@Override
|
||||||
final String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||||
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
Player targetedPlayer) {
|
||||||
|
final Card host = sa.getHostCard();
|
||||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
final Player ctrl = host.getController();
|
||||||
if (!newOptions.isEmpty()) {
|
|
||||||
options = newOptions;
|
final boolean isVesuva = "Vesuva".equals(host.getName());
|
||||||
}
|
|
||||||
Card choice = ComputerUtilCard.getBestAI(options);
|
final String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
||||||
if (isVesuva && "Vesuva".equals(choice.getName())) {
|
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
||||||
choice = null;
|
|
||||||
}
|
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||||
|
if (!newOptions.isEmpty()) {
|
||||||
return choice;
|
options = newOptions;
|
||||||
}
|
}
|
||||||
|
Card choice = ComputerUtilCard.getBestAI(options);
|
||||||
}
|
if (isVesuva && "Vesuva".equals(choice.getName())) {
|
||||||
|
choice = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return choice;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,112 +1,113 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.player.Player;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.util.MyRandom;
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.MyRandom;
|
||||||
public class ControlExchangeAi extends SpellAbilityAi {
|
|
||||||
|
public class ControlExchangeAi extends SpellAbilityAi {
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
/* (non-Javadoc)
|
||||||
*/
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
@Override
|
*/
|
||||||
protected boolean canPlayAI(Player ai, final SpellAbility sa) {
|
@Override
|
||||||
Card object1 = null;
|
protected boolean canPlayAI(Player ai, final SpellAbility sa) {
|
||||||
Card object2 = null;
|
Card object1 = null;
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
Card object2 = null;
|
||||||
sa.resetTargets();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
sa.resetTargets();
|
||||||
CardCollection list =
|
|
||||||
CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
CardCollection list =
|
||||||
// AI won't try to grab cards that are filtered out of AI decks on
|
CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||||
// purpose
|
// AI won't try to grab cards that are filtered out of AI decks on
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
// purpose
|
||||||
@Override
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
public boolean apply(final Card c) {
|
@Override
|
||||||
return !c.hasSVar("RemAIDeck") && c.canBeTargetedBy(sa);
|
public boolean apply(final Card c) {
|
||||||
}
|
return !c.hasSVar("RemAIDeck") && c.canBeTargetedBy(sa);
|
||||||
});
|
}
|
||||||
object1 = ComputerUtilCard.getBestAI(list);
|
});
|
||||||
if (sa.hasParam("Defined")) {
|
object1 = ComputerUtilCard.getBestAI(list);
|
||||||
object2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
|
if (sa.hasParam("Defined")) {
|
||||||
} else if (tgt.getMinTargets(sa.getHostCard(), sa) > 1) {
|
object2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
|
||||||
CardCollectionView list2 = ai.getCardsIn(ZoneType.Battlefield);
|
} else if (tgt.getMinTargets(sa.getHostCard(), sa) > 1) {
|
||||||
list2 = CardLists.getValidCards(list2, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
CardCollectionView list2 = ai.getCardsIn(ZoneType.Battlefield);
|
||||||
object2 = ComputerUtilCard.getWorstAI(list2);
|
list2 = CardLists.getValidCards(list2, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||||
sa.getTargets().add(object2);
|
object2 = ComputerUtilCard.getWorstAI(list2);
|
||||||
}
|
sa.getTargets().add(object2);
|
||||||
if (object1 == null || object2 == null) {
|
}
|
||||||
return false;
|
if (object1 == null || object2 == null) {
|
||||||
}
|
return false;
|
||||||
if (ComputerUtilCard.evaluateCreature(object1) > ComputerUtilCard.evaluateCreature(object2) + 40) {
|
}
|
||||||
sa.getTargets().add(object1);
|
if (ComputerUtilCard.evaluateCreature(object1) > ComputerUtilCard.evaluateCreature(object2) + 40) {
|
||||||
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
sa.getTargets().add(object1);
|
||||||
}
|
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
return false;
|
}
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
@Override
|
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
@Override
|
||||||
if (!sa.usesTargeting()) {
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
if (mandatory) {
|
if (!sa.usesTargeting()) {
|
||||||
return true;
|
if (mandatory) {
|
||||||
}
|
return true;
|
||||||
} else {
|
}
|
||||||
if (mandatory) {
|
} else {
|
||||||
return chkAIDrawback(sa, aiPlayer);
|
if (mandatory) {
|
||||||
} else {
|
return chkAIDrawback(sa, aiPlayer);
|
||||||
return canPlayAI(aiPlayer, sa);
|
} else {
|
||||||
}
|
return canPlayAI(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
@Override
|
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
@Override
|
||||||
if (!sa.usesTargeting()) {
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
return true;
|
if (!sa.usesTargeting()) {
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield),
|
|
||||||
tgt.getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield),
|
||||||
|
tgt.getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
||||||
// only select the cards that can be targeted
|
|
||||||
list = CardLists.getTargetableCards(list, sa);
|
// only select the cards that can be targeted
|
||||||
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
if (list.isEmpty())
|
|
||||||
return false;
|
if (list.isEmpty())
|
||||||
|
return false;
|
||||||
Card best = ComputerUtilCard.getBestAI(list);
|
|
||||||
|
Card best = ComputerUtilCard.getBestAI(list);
|
||||||
// if Param has Defined, check if the best Target is better than the Defined
|
|
||||||
if (sa.hasParam("Defined")) {
|
// if Param has Defined, check if the best Target is better than the Defined
|
||||||
final Card object = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
|
if (sa.hasParam("Defined")) {
|
||||||
// TODO add evaluate Land if able
|
final Card object = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
|
||||||
final Card realBest = ComputerUtilCard.getBestAI(Lists.newArrayList(best, object));
|
// TODO add evaluate Land if able
|
||||||
|
final Card realBest = ComputerUtilCard.getBestAI(Lists.newArrayList(best, object));
|
||||||
// Defined card is better than this one, try to avoid trade
|
|
||||||
if (!best.equals(realBest)) {
|
// Defined card is better than this one, try to avoid trade
|
||||||
return false;
|
if (!best.equals(realBest)) {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// add best Target
|
|
||||||
sa.getTargets().add(best);
|
// add best Target
|
||||||
return true;
|
sa.getTargets().add(best);
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,317 +1,319 @@
|
|||||||
/*
|
/*
|
||||||
* Forge: Play Magic: the Gathering.
|
* Forge: Play Magic: the Gathering.
|
||||||
* Copyright (C) 2011 Forge Team
|
* Copyright (C) 2011 Forge Team
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
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;
|
||||||
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.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
|
|
||||||
|
|
||||||
//AB:GainControl|ValidTgts$Creature|TgtPrompt$Select target legendary creature|LoseControl$Untap,LoseControl|SpellDescription$Gain control of target xxxxxxx
|
//AB:GainControl|ValidTgts$Creature|TgtPrompt$Select target legendary creature|LoseControl$Untap,LoseControl|SpellDescription$Gain control of target xxxxxxx
|
||||||
|
|
||||||
//GainControl specific sa:
|
//GainControl specific sa:
|
||||||
// LoseControl - the lose control conditions (as a comma separated list)
|
// LoseControl - the lose control conditions (as a comma separated list)
|
||||||
// -Untap - source card becomes untapped
|
// -Untap - source card becomes untapped
|
||||||
// -LoseControl - you lose control of source card
|
// -LoseControl - you lose control of source card
|
||||||
// -LeavesPlay - source card leaves the battlefield
|
// -LeavesPlay - source card leaves the battlefield
|
||||||
// -PowerGT - (not implemented yet for Old Man of the Sea)
|
// -PowerGT - (not implemented yet for Old Man of the Sea)
|
||||||
// AddKWs - Keywords to add to the controlled card
|
// AddKWs - Keywords to add to the controlled card
|
||||||
// (as a "&"-separated list; like Haste, Sacrifice CARDNAME at EOT, any standard keyword)
|
// (as a "&"-separated list; like Haste, Sacrifice CARDNAME at EOT, any standard keyword)
|
||||||
// OppChoice - set to True if opponent chooses creature (for Preacher) - not implemented yet
|
// OppChoice - set to True if opponent chooses creature (for Preacher) - not implemented yet
|
||||||
// Untap - set to True if target card should untap when control is taken
|
// Untap - set to True if target card should untap when control is taken
|
||||||
// DestroyTgt - actions upon which the tgt should be destroyed. same list as LoseControl
|
// DestroyTgt - actions upon which the tgt should be destroyed. same list as LoseControl
|
||||||
// NoRegen - set if destroyed creature can't be regenerated. used only with DestroyTgt
|
// NoRegen - set if destroyed creature can't be regenerated. used only with DestroyTgt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* AbilityFactory_GainControl class.
|
* AbilityFactory_GainControl class.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author Forge
|
* @author Forge
|
||||||
* @version $Id: AbilityFactoryGainControl.java 17764 2012-10-29 11:04:18Z Sloth $
|
* @version $Id: AbilityFactoryGainControl.java 17764 2012-10-29 11:04:18Z Sloth $
|
||||||
*/
|
*/
|
||||||
public class ControlGainAi extends SpellAbilityAi {
|
public class ControlGainAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
protected boolean canPlayAI(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")) {
|
||||||
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
|
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
|
||||||
}
|
}
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final FCollectionView<Player> opponents = ai.getOpponents();
|
final FCollectionView<Player> opponents = ai.getOpponents();
|
||||||
|
|
||||||
// if Defined, then don't worry about targeting
|
// if Defined, then don't worry about targeting
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
if (sa.hasParam("AllValid")) {
|
if (sa.hasParam("AllValid")) {
|
||||||
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), opponents);
|
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), opponents);
|
||||||
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 false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} 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);
|
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||||
}
|
}
|
||||||
if (tgt.isRandomTarget()) {
|
if (tgt.isRandomTarget()) {
|
||||||
sa.getTargets().add(Aggregates.random(tgt.getAllCandidates(sa, false)));
|
sa.getTargets().add(Aggregates.random(tgt.getAllCandidates(sa, false)));
|
||||||
}
|
}
|
||||||
if (tgt.canOnlyTgtOpponent()) {
|
if (tgt.canOnlyTgtOpponent()) {
|
||||||
List<Player> oppList = Lists
|
List<Player> oppList = Lists
|
||||||
.newArrayList(Iterables.filter(opponents, PlayerPredicates.isTargetableBy(sa)));
|
.newArrayList(Iterables.filter(opponents, PlayerPredicates.isTargetableBy(sa)));
|
||||||
|
|
||||||
if (oppList.isEmpty()) {
|
if (oppList.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.getTargets().add(oppList.get(0));
|
sa.getTargets().add(oppList.get(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't steal something if I can't Attack without, or prevent it from
|
// Don't steal something if I can't Attack without, or prevent it from
|
||||||
// blocking at least
|
// blocking at least
|
||||||
if (lose.contains("EOT")
|
if (lose.contains("EOT")
|
||||||
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
&& !sa.isTrigger()) {
|
&& !sa.isTrigger()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 true;
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection list = new CardCollection();
|
CardCollection list = new CardCollection();
|
||||||
for (Player pl : opponents) {
|
for (Player pl : opponents) {
|
||||||
list.addAll(pl.getCardsIn(ZoneType.Battlefield));
|
list.addAll(pl.getCardsIn(ZoneType.Battlefield));
|
||||||
}
|
}
|
||||||
|
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
// no valid targets, so we need to bail
|
// no valid targets, so we need to bail
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
if (!c.canBeTargetedBy(sa)) {
|
if (!c.canBeTargetedBy(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (sa.isTrigger()) {
|
if (sa.isTrigger()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not take perm control on something that leaves the play end of turn
|
// do not take perm control on something that leaves the play end of turn
|
||||||
if (!lose.contains("EOT") && c.hasSVar("EndOfTurnLeavePlay")) {
|
if (!lose.contains("EOT") && c.hasSVar("EndOfTurnLeavePlay")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c.isCreature()) {
|
if (c.isCreature()) {
|
||||||
if (c.getNetCombatDamage() <= 0) {
|
if (c.getNetCombatDamage() <= 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// can not attack any opponent
|
// can not attack any opponent
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
for (final Player opp : opponents) {
|
for (final Player opp : opponents) {
|
||||||
if (ComputerUtilCombat.canAttackNextTurn(c, opp)) {
|
if (ComputerUtilCombat.canAttackNextTurn(c, opp)) {
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not take control on something it doesn't know how to use
|
// do not take control on something it doesn't know how to use
|
||||||
return !c.hasSVar("RemAIDeck");
|
return !c.hasSVar("RemAIDeck");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int creatures = 0, artifacts = 0, planeswalkers = 0, lands = 0, enchantments = 0;
|
int creatures = 0, artifacts = 0, planeswalkers = 0, lands = 0, enchantments = 0;
|
||||||
|
|
||||||
for (final Card c : list) {
|
for (final Card c : list) {
|
||||||
if (c.isCreature()) {
|
if (c.isCreature()) {
|
||||||
creatures++;
|
creatures++;
|
||||||
}
|
}
|
||||||
if (c.isArtifact()) {
|
if (c.isArtifact()) {
|
||||||
artifacts++;
|
artifacts++;
|
||||||
}
|
}
|
||||||
if (c.isLand()) {
|
if (c.isLand()) {
|
||||||
lands++;
|
lands++;
|
||||||
}
|
}
|
||||||
if (c.isEnchantment()) {
|
if (c.isEnchantment()) {
|
||||||
enchantments++;
|
enchantments++;
|
||||||
}
|
}
|
||||||
if (c.isPlaneswalker()) {
|
if (c.isPlaneswalker()) {
|
||||||
planeswalkers++;
|
planeswalkers++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
Card t = null;
|
Card t = null;
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
// TODO is this good enough? for up to amounts?
|
// TODO is this good enough? for up to amounts?
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (t == null) {
|
while (t == null) {
|
||||||
if (planeswalkers > 0) {
|
if (planeswalkers > 0) {
|
||||||
t = ComputerUtilCard.getBestPlaneswalkerAI(list);
|
t = ComputerUtilCard.getBestPlaneswalkerAI(list);
|
||||||
} else if (creatures > 0) {
|
} else if (creatures > 0) {
|
||||||
t = ComputerUtilCard.getBestCreatureAI(list);
|
t = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
} else if (artifacts > 0) {
|
} else if (artifacts > 0) {
|
||||||
t = ComputerUtilCard.getBestArtifactAI(list);
|
t = ComputerUtilCard.getBestArtifactAI(list);
|
||||||
} else if (lands > 0) {
|
} else if (lands > 0) {
|
||||||
t = ComputerUtilCard.getBestLandAI(list);
|
t = ComputerUtilCard.getBestLandAI(list);
|
||||||
} else if (enchantments > 0) {
|
} else if (enchantments > 0) {
|
||||||
t = ComputerUtilCard.getBestEnchantmentAI(list, sa, true);
|
t = ComputerUtilCard.getBestEnchantmentAI(list, sa, true);
|
||||||
} else {
|
} else {
|
||||||
t = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
t = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t.isCreature())
|
if (t != null) {
|
||||||
creatures--;
|
if (t.isCreature())
|
||||||
if (t.isPlaneswalker())
|
creatures--;
|
||||||
planeswalkers--;
|
if (t.isPlaneswalker())
|
||||||
if (t.isLand())
|
planeswalkers--;
|
||||||
lands--;
|
if (t.isLand())
|
||||||
if (t.isArtifact())
|
lands--;
|
||||||
artifacts--;
|
if (t.isArtifact())
|
||||||
if (t.isEnchantment())
|
artifacts--;
|
||||||
enchantments--;
|
if (t.isEnchantment())
|
||||||
|
enchantments--;
|
||||||
if (!sa.canTarget(t)) {
|
}
|
||||||
list.remove(t);
|
|
||||||
t = null;
|
if (!sa.canTarget(t)) {
|
||||||
if (list.isEmpty()) {
|
list.remove(t);
|
||||||
break;
|
t = null;
|
||||||
}
|
if (list.isEmpty()) {
|
||||||
}
|
break;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
if (t != null) {
|
};
|
||||||
sa.getTargets().add(t);
|
|
||||||
list.remove(t);
|
if (t != null) {
|
||||||
}
|
sa.getTargets().add(t);
|
||||||
}
|
list.remove(t);
|
||||||
|
}
|
||||||
return true;
|
}
|
||||||
|
|
||||||
}
|
return true;
|
||||||
|
|
||||||
@Override
|
}
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
|
||||||
if (sa.getTargetRestrictions() == null) {
|
@Override
|
||||||
if (mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
return true;
|
if (sa.getTargetRestrictions() == null) {
|
||||||
}
|
if (mandatory) {
|
||||||
} else {
|
return true;
|
||||||
if(sa.hasParam("TargetingPlayer") || (!this.canPlayAI(ai, sa) && mandatory)) {
|
}
|
||||||
List<Card> list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
} else {
|
||||||
if (list.isEmpty()) {
|
if(sa.hasParam("TargetingPlayer") || (!this.canPlayAI(ai, sa) && mandatory)) {
|
||||||
return false;
|
List<Card> list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
} else {
|
if (list.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
return false;
|
||||||
}
|
} else {
|
||||||
}
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
return true;
|
||||||
@Override
|
}
|
||||||
public boolean chkAIDrawback(SpellAbility sa, final Player ai) {
|
|
||||||
final Game game = ai.getGame();
|
@Override
|
||||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
public boolean chkAIDrawback(SpellAbility sa, final Player ai) {
|
||||||
if (sa.hasParam("AllValid")) {
|
final Game game = ai.getGame();
|
||||||
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||||
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
if (sa.hasParam("AllValid")) {
|
||||||
if (tgtCards.isEmpty()) {
|
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||||
return false;
|
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
||||||
}
|
if (tgtCards.isEmpty()) {
|
||||||
}
|
return false;
|
||||||
final List<String> lose = Lists.newArrayList();
|
}
|
||||||
|
}
|
||||||
if (sa.hasParam("LoseControl")) {
|
final List<String> lose = Lists.newArrayList();
|
||||||
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
|
|
||||||
}
|
if (sa.hasParam("LoseControl")) {
|
||||||
|
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
|
||||||
if (lose.contains("EOT")
|
}
|
||||||
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
|
||||||
return false;
|
if (lose.contains("EOT")
|
||||||
}
|
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
} else {
|
return false;
|
||||||
return this.canPlayAI(ai, sa);
|
}
|
||||||
}
|
} else {
|
||||||
|
return this.canPlayAI(ai, sa);
|
||||||
return true;
|
}
|
||||||
} // pumpDrawbackAI()
|
|
||||||
|
return true;
|
||||||
@Override
|
} // pumpDrawbackAI()
|
||||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
|
||||||
final List<Card> cards = Lists.newArrayList();
|
@Override
|
||||||
for (Player p : options) {
|
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||||
cards.addAll(p.getCreaturesInPlay());
|
final List<Card> cards = Lists.newArrayList();
|
||||||
}
|
for (Player p : options) {
|
||||||
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
|
cards.addAll(p.getCreaturesInPlay());
|
||||||
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
|
}
|
||||||
}
|
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
|
||||||
}
|
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,154 +1,195 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.base.Predicates;
|
import forge.ai.*;
|
||||||
import com.google.common.collect.Iterables;
|
import forge.game.Game;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.game.card.*;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.card.Card;
|
import forge.game.player.Player;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.player.PlayerCollection;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.game.card.CardUtil;
|
|
||||||
import forge.game.phase.PhaseType;
|
import java.util.Collection;
|
||||||
import forge.game.player.Player;
|
import java.util.List;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
|
||||||
import forge.game.player.PlayerCollection;
|
public class CopyPermanentAi extends SpellAbilityAi {
|
||||||
import forge.game.spellability.SpellAbility;
|
@Override
|
||||||
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
public class CopyPermanentAi extends SpellAbilityAi {
|
// Card source = sa.getHostCard();
|
||||||
@Override
|
// TODO - I'm sure someone can do this AI better
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
|
||||||
// Card source = sa.getHostCard();
|
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
||||||
// TODO - I'm sure someone can do this AI better
|
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) {
|
if ("MimicVat".equals(aiLogic)) {
|
||||||
return false;
|
return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
|
||||||
}
|
} else if ("AtEOT".equals(aiLogic)) {
|
||||||
|
return ph.is(PhaseType.END_OF_TURN);
|
||||||
if (sa.hasParam("Defined")) {
|
} else if ("AtOppEOT".equals(aiLogic)) {
|
||||||
// If there needs to be an imprinted card, don't activate the ability if nothing was imprinted yet (e.g. Mimic Vat)
|
return ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn() != aiPlayer;
|
||||||
if (sa.getParam("Defined").equals("Imprinted.ExiledWithSource") && sa.getHostCard().getImprintedCards().isEmpty()) {
|
}
|
||||||
return false;
|
|
||||||
}
|
if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
if (sa.usesTargeting() && sa.hasParam("TargetingPlayer")) {
|
|
||||||
sa.resetTargets();
|
if (sa.hasParam("Defined")) {
|
||||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
|
// If there needs to be an imprinted card, don't activate the ability if nothing was imprinted yet (e.g. Mimic Vat)
|
||||||
sa.setTargetingPlayer(targetingPlayer);
|
if (sa.getParam("Defined").equals("Imprinted.ExiledWithSource") && sa.getHostCard().getImprintedCards().isEmpty()) {
|
||||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
return false;
|
||||||
} else {
|
}
|
||||||
return this.doTriggerAINoCost(aiPlayer, sa, false);
|
}
|
||||||
}
|
|
||||||
}
|
if (sa.hasParam("Embalm") || sa.hasParam("Eternalize")) {
|
||||||
|
// E.g. Vizier of Many Faces: check to make sure it makes sense to make the token now
|
||||||
@Override
|
if (ComputerUtilCard.checkNeedsToPlayReqs(sa.getHostCard(), sa) != AiPlayDecision.WillPlay) {
|
||||||
protected boolean doTriggerAINoCost(final Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
return false;
|
||||||
// ////
|
}
|
||||||
// Targeting
|
}
|
||||||
if (sa.usesTargeting()) {
|
|
||||||
sa.resetTargets();
|
if (sa.usesTargeting() && sa.hasParam("TargetingPlayer")) {
|
||||||
|
sa.resetTargets();
|
||||||
CardCollection list = new CardCollection(CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa));
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
|
sa.setTargetingPlayer(targetingPlayer);
|
||||||
list = CardLists.filter(list, Predicates.not(CardPredicates.hasSVar("RemAIDeck")));
|
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||||
//Nothing to target
|
} else {
|
||||||
if (list.isEmpty()) {
|
return this.doTriggerAINoCost(aiPlayer, sa, false);
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saheeli Rai + Felidar Guardian combo support
|
@Override
|
||||||
if (sa.getHostCard().getName().equals("Saheeli Rai")) {
|
protected boolean doTriggerAINoCost(final Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
CardCollection felidarGuardian = CardLists.filter(list, CardPredicates.nameEquals("Felidar Guardian"));
|
final Card host = sa.getHostCard();
|
||||||
if (felidarGuardian.size() > 0) {
|
final Player activator = sa.getActivatingPlayer();
|
||||||
// can copy a Felidar Guardian and combo off, so let's do it
|
final Game game = host.getGame();
|
||||||
sa.getTargets().add(felidarGuardian.get(0));
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
// ////
|
||||||
|
// Targeting
|
||||||
// target loop
|
if (sa.usesTargeting()) {
|
||||||
while (sa.canAddMoreTarget()) {
|
sa.resetTargets();
|
||||||
if (list.isEmpty()) {
|
|
||||||
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
|
CardCollection list = new CardCollection(CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa));
|
||||||
sa.resetTargets();
|
|
||||||
return false;
|
list = CardLists.filter(list, Predicates.not(CardPredicates.hasSVar("RemAIDeck")));
|
||||||
} else {
|
//Nothing to target
|
||||||
// TODO is this good enough? for up to amounts?
|
if (list.isEmpty()) {
|
||||||
break;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Saheeli Rai + Felidar Guardian combo support
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
if ("Saheeli Rai".equals(sourceName)) {
|
||||||
@Override
|
CardCollection felidarGuardian = CardLists.filter(list, CardPredicates.nameEquals("Felidar Guardian"));
|
||||||
public boolean apply(final Card c) {
|
if (felidarGuardian.size() > 0) {
|
||||||
return !c.getType().isLegendary() || !c.getController().equals(aiPlayer);
|
// can copy a Felidar Guardian and combo off, so let's do it
|
||||||
}
|
sa.getTargets().add(felidarGuardian.get(0));
|
||||||
});
|
return true;
|
||||||
Card choice;
|
}
|
||||||
if (!CardLists.filter(list, Presets.CREATURES).isEmpty()) {
|
}
|
||||||
if (sa.hasParam("TargetingPlayer")) {
|
|
||||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
// target loop
|
||||||
} else {
|
while (sa.canAddMoreTarget()) {
|
||||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
if (list.isEmpty()) {
|
||||||
}
|
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
|
||||||
} else {
|
sa.resetTargets();
|
||||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
return false;
|
||||||
}
|
} else {
|
||||||
|
// TODO is this good enough? for up to amounts?
|
||||||
if (choice == null) { // can't find anything left
|
break;
|
||||||
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
|
}
|
||||||
sa.resetTargets();
|
}
|
||||||
return false;
|
|
||||||
} else {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
// TODO is this good enough? for up to amounts?
|
@Override
|
||||||
break;
|
public boolean apply(final Card c) {
|
||||||
}
|
return !c.getType().isLegendary() || !c.getController().equals(aiPlayer);
|
||||||
}
|
}
|
||||||
list.remove(choice);
|
});
|
||||||
sa.getTargets().add(choice);
|
Card choice;
|
||||||
}
|
if (!CardLists.filter(list, Presets.CREATURES).isEmpty()) {
|
||||||
} else {
|
if (sa.hasParam("TargetingPlayer")) {
|
||||||
// if no targeting, it should always be ok
|
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||||
}
|
} else {
|
||||||
|
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
return true;
|
}
|
||||||
}
|
} else {
|
||||||
|
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||||
/* (non-Javadoc)
|
}
|
||||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
|
||||||
*/
|
if (choice == null) { // can't find anything left
|
||||||
@Override
|
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
sa.resetTargets();
|
||||||
//TODO: add logic here
|
return false;
|
||||||
return true;
|
} else {
|
||||||
}
|
// TODO is this good enough? for up to amounts?
|
||||||
|
break;
|
||||||
/* (non-Javadoc)
|
}
|
||||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
}
|
||||||
*/
|
list.remove(choice);
|
||||||
@Override
|
sa.getTargets().add(choice);
|
||||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
}
|
||||||
// Select a card to attach to
|
} else if (sa.hasParam("Choices")) {
|
||||||
return ComputerUtilCard.getBestAI(options);
|
// only check for options, does not select there
|
||||||
}
|
CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
|
||||||
|
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host);
|
||||||
@Override
|
Collection<Card> betterChoices = getBetterOptions(aiPlayer, sa, choices, !mandatory);
|
||||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
if (betterChoices.isEmpty()) {
|
||||||
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay();
|
return mandatory;
|
||||||
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
|
}
|
||||||
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
|
} else {
|
||||||
}
|
// if no targeting, it should always be ok
|
||||||
|
}
|
||||||
}
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
|
//TODO: add logic here
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||||
|
// Select a card to attach to
|
||||||
|
CardCollection betterOptions = getBetterOptions(ai, sa, options, isOptional);
|
||||||
|
if (!betterOptions.isEmpty()) {
|
||||||
|
options = betterOptions;
|
||||||
|
}
|
||||||
|
return ComputerUtilCard.getBestAI(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CardCollection getBetterOptions(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional) {
|
||||||
|
final Card host = sa.getHostCard();
|
||||||
|
final Player ctrl = host.getController();
|
||||||
|
final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
|
||||||
|
// TODO add filter to not select Legendary from Other Player when ai already have a Legendary with that name
|
||||||
|
return CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||||
|
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay();
|
||||||
|
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
|
||||||
|
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package forge.ai.ability;
|
|||||||
import forge.ai.SpecialCardAi;
|
import forge.ai.SpecialCardAi;
|
||||||
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.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class CopySpellAbilityAi extends SpellAbilityAi {
|
public class CopySpellAbilityAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -25,17 +27,31 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
|||||||
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
||||||
// NOTE: Other SAs that use CopySpellAbilityAi (e.g. Chain Lightning) are currently routed through
|
// NOTE: Other SAs that use CopySpellAbilityAi (e.g. Chain Lightning) are currently routed through
|
||||||
// generic method SpellAbilityAi#chkDrawbackWithSubs and are handled there.
|
// generic method SpellAbilityAi#chkDrawbackWithSubs and are handled there.
|
||||||
|
|
||||||
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"))) {
|
||||||
|
return SpecialCardAi.ChainOfAcid.consider(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.chkAIDrawback(sa, aiPlayer);
|
return super.chkAIDrawback(sa, aiPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
|
||||||
|
Map<String, Object> params) {
|
||||||
return spells.get(0);
|
return spells.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
|
// 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.
|
||||||
|
if ("ChainOfAcid".equals(sa.getParam("AILogic"))) {
|
||||||
|
return SpecialCardAi.ChainOfAcid.consider(player, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,299 +1,350 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.AiController;
|
import forge.ai.*;
|
||||||
import forge.ai.AiProps;
|
import forge.game.Game;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.game.ability.AbilityUtils;
|
||||||
import java.util.Iterator;
|
import forge.game.ability.ApiType;
|
||||||
|
import forge.game.card.Card;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.game.card.CardFactoryUtil;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.game.cost.Cost;
|
||||||
import forge.ai.PlayerControllerAi;
|
import forge.game.cost.CostDiscard;
|
||||||
import forge.ai.SpecialCardAi;
|
import forge.game.cost.CostExile;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.game.cost.CostSacrifice;
|
||||||
import forge.game.Game;
|
import forge.game.player.Player;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
import forge.game.card.Card;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.card.CardFactoryUtil;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.game.cost.Cost;
|
import forge.util.MyRandom;
|
||||||
import forge.game.player.Player;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import forge.game.spellability.SpellAbility;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
import forge.game.spellability.SpellAbilityStackInstance;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.util.MyRandom;
|
import java.util.Iterator;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
public class CounterAi extends SpellAbilityAi {
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
|
||||||
|
@Override
|
||||||
public class CounterAi extends SpellAbilityAi {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
boolean toReturn = true;
|
||||||
@Override
|
final Cost abCost = sa.getPayCosts();
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
final Card source = sa.getHostCard();
|
||||||
boolean toReturn = true;
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Game game = ai.getGame();
|
||||||
final Card source = sa.getHostCard();
|
int tgtCMC = 0;
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
SpellAbility tgtSA = null;
|
||||||
final Game game = ai.getGame();
|
|
||||||
int tgtCMC = 0;
|
if (game.getStack().isEmpty()) {
|
||||||
SpellAbility tgtSA = null;
|
return false;
|
||||||
|
}
|
||||||
if (game.getStack().isEmpty()) {
|
|
||||||
return false;
|
if (abCost != null) {
|
||||||
}
|
// AI currently disabled for these costs
|
||||||
|
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||||
if (abCost != null) {
|
return false;
|
||||||
// AI currently disabled for these costs
|
}
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
}
|
||||||
return false;
|
|
||||||
}
|
if ("Force of Will".equals(sourceName)) {
|
||||||
}
|
if (!SpecialCardAi.ForceOfWill.consider(ai, sa)) {
|
||||||
|
return false;
|
||||||
if ("Force of Will".equals(sourceName)) {
|
}
|
||||||
if (!SpecialCardAi.ForceOfWill.consider(ai, sa)) {
|
}
|
||||||
return false;
|
|
||||||
}
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
}
|
if (tgt != null) {
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final SpellAbility topSA = ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
|
||||||
if (tgt != null) {
|
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai
|
||||||
|
|| ai.getAllies().contains(topSA.getActivatingPlayer())) {
|
||||||
final SpellAbility topSA = ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
|
// might as well check for player's friendliness
|
||||||
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai
|
return false;
|
||||||
|| ai.getAllies().contains(topSA.getActivatingPlayer())) {
|
}
|
||||||
// might as well check for player's friendliness
|
|
||||||
return false;
|
// 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 false;
|
}
|
||||||
}
|
|
||||||
|
if (sa.hasParam("CounterNoManaSpell") && topSA.getTotalManaSpent() > 0) {
|
||||||
if (sa.hasParam("CounterNoManaSpell") && topSA.getTotalManaSpent() > 0) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
if ("OppDiscardsHand".equals(sa.getParam("AILogic"))) {
|
||||||
sa.resetTargets();
|
if (topSA.getActivatingPlayer().getCardsIn(ZoneType.Hand).size() < 2) {
|
||||||
if (sa.canTargetSpellAbility(topSA)) {
|
return false;
|
||||||
sa.getTargets().add(topSA);
|
}
|
||||||
if (topSA.getPayCosts().getTotalMana() != null) {
|
}
|
||||||
tgtSA = topSA;
|
|
||||||
tgtCMC = topSA.getPayCosts().getTotalMana().getCMC();
|
sa.resetTargets();
|
||||||
tgtCMC += topSA.getPayCosts().getTotalMana().countX() > 0 ? 3 : 0; // TODO: somehow determine the value of X paid and account for it?
|
if (sa.canTargetSpellAbility(topSA)) {
|
||||||
}
|
sa.getTargets().add(topSA);
|
||||||
} else {
|
if (topSA.getPayCosts().getTotalMana() != null) {
|
||||||
return false;
|
tgtSA = topSA;
|
||||||
}
|
tgtCMC = topSA.getPayCosts().getTotalMana().getCMC();
|
||||||
} else {
|
tgtCMC += topSA.getPayCosts().getTotalMana().countX() > 0 ? 3 : 0; // TODO: somehow determine the value of X paid and account for it?
|
||||||
return false;
|
}
|
||||||
}
|
} else {
|
||||||
|
return false;
|
||||||
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
|
}
|
||||||
|
} else {
|
||||||
if (unlessCost != null && !unlessCost.endsWith(">")) {
|
return false;
|
||||||
// Is this Usable Mana Sources? Or Total Available Mana?
|
}
|
||||||
final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size();
|
|
||||||
int toPay = 0;
|
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
|
||||||
boolean setPayX = false;
|
|
||||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
if (unlessCost != null && !unlessCost.endsWith(">")) {
|
||||||
setPayX = true;
|
Player opp = tgtSA.getActivatingPlayer();
|
||||||
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
int usableManaSources = ComputerUtilMana.getAvailableManaEstimate(opp);
|
||||||
} else {
|
|
||||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
int toPay = 0;
|
||||||
}
|
boolean setPayX = false;
|
||||||
|
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||||
if (toPay == 0) {
|
setPayX = true;
|
||||||
return false;
|
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
}
|
} else {
|
||||||
|
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||||
if (toPay <= usableManaSources) {
|
}
|
||||||
// If this is a reusable Resource, feel free to play it most of
|
|
||||||
// the time
|
if (toPay == 0) {
|
||||||
if (!SpellAbilityAi.playReusable(ai,sa)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
}
|
if (toPay <= usableManaSources) {
|
||||||
|
// If this is a reusable Resource, feel free to play it most of
|
||||||
if (setPayX) {
|
// the time
|
||||||
source.setSVar("PayX", Integer.toString(toPay));
|
if (!SpellAbilityAi.playReusable(ai,sa)) {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// TODO Improve AI
|
|
||||||
|
if (setPayX) {
|
||||||
// Will return true if this spell can counter (or is Reusable and can
|
source.setSVar("PayX", Integer.toString(toPay));
|
||||||
// force the Human into making decisions)
|
}
|
||||||
|
}
|
||||||
// But really it should be more picky about how it counters things
|
|
||||||
|
// TODO Improve AI
|
||||||
if (sa.hasParam("AILogic")) {
|
|
||||||
String logic = sa.getParam("AILogic");
|
// Will return true if this spell can counter (or is Reusable and can
|
||||||
if ("Never".equals(logic)) {
|
// force the Human into making decisions)
|
||||||
return false;
|
|
||||||
} else if (logic.startsWith("MinCMC.")) {
|
// But really it should be more picky about how it counters things
|
||||||
int minCMC = Integer.parseInt(logic.substring(7));
|
|
||||||
if (tgtCMC < minCMC) {
|
if (sa.hasParam("AILogic")) {
|
||||||
return false;
|
String logic = sa.getParam("AILogic");
|
||||||
}
|
if ("Never".equals(logic)) {
|
||||||
}
|
return false;
|
||||||
}
|
} else if (logic.startsWith("MinCMC.")) {
|
||||||
|
int minCMC = Integer.parseInt(logic.substring(7));
|
||||||
// Specific constraints for the AI to use/not use counterspells against specific groups of spells
|
if (tgtCMC < minCMC) {
|
||||||
// (specified in the AI profile)
|
return false;
|
||||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
}
|
||||||
boolean ctrCmc0ManaPerms = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS);
|
} else if ("NullBrooch".equals(logic)) {
|
||||||
boolean ctrDamageSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_DAMAGE_SPELLS);
|
if (!SpecialCardAi.NullBrooch.consider(ai, sa)) {
|
||||||
boolean ctrRemovalSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_REMOVAL_SPELLS);
|
return false;
|
||||||
boolean ctrOtherCounters = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_OTHER_COUNTERSPELLS);
|
}
|
||||||
String ctrNamed = aic.getProperty(AiProps.ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS);
|
}
|
||||||
if (tgtSA != null && tgtCMC < aic.getIntProperty(AiProps.MIN_SPELL_CMC_TO_COUNTER)) {
|
}
|
||||||
boolean dontCounter = true;
|
|
||||||
Card tgtSource = tgtSA.getHostCard();
|
// Specific constraints for the AI to use/not use counterspells against specific groups of spells
|
||||||
if ((tgtSource != null && tgtCMC == 0 && tgtSource.isPermanent() && !tgtSource.getManaAbilities().isEmpty() && ctrCmc0ManaPerms)
|
// (specified in the AI profile)
|
||||||
|| (tgtSA.getApi() == ApiType.DealDamage || tgtSA.getApi() == ApiType.LoseLife || tgtSA.getApi() == ApiType.DamageAll && ctrDamageSpells)
|
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
|| (tgtSA.getApi() == ApiType.Counter && ctrOtherCounters)
|
boolean ctrCmc0ManaPerms = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS);
|
||||||
|| (tgtSA.getApi() == ApiType.Destroy || tgtSA.getApi() == ApiType.DestroyAll || tgtSA.getApi() == ApiType.Sacrifice
|
boolean ctrDamageSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_DAMAGE_SPELLS);
|
||||||
|| tgtSA.getApi() == ApiType.SacrificeAll && ctrRemovalSpells)) {
|
boolean ctrRemovalSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_REMOVAL_SPELLS);
|
||||||
dontCounter = false;
|
boolean ctrPumpSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_PUMP_SPELLS);
|
||||||
}
|
boolean ctrAuraSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_AURAS);
|
||||||
|
boolean ctrOtherCounters = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_OTHER_COUNTERSPELLS);
|
||||||
if (tgtSource != null && !ctrNamed.isEmpty() && !"none".equalsIgnoreCase(ctrNamed)) {
|
int ctrChanceCMC1 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_1);
|
||||||
for (String name : StringUtils.split(ctrNamed, ";")) {
|
int ctrChanceCMC2 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_2);
|
||||||
if (name.equals(tgtSource.getName())) {
|
int ctrChanceCMC3 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_3);
|
||||||
dontCounter = false;
|
String ctrNamed = aic.getProperty(AiProps.ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS);
|
||||||
}
|
boolean dontCounter = false;
|
||||||
}
|
|
||||||
}
|
if (tgtCMC == 1 && !MyRandom.percentTrue(ctrChanceCMC1)) {
|
||||||
|
dontCounter = true;
|
||||||
// should always counter CMC 1 with Mental Misstep despite a possible limitation by minimum CMC
|
} else if (tgtCMC == 2 && !MyRandom.percentTrue(ctrChanceCMC2)) {
|
||||||
if (tgtCMC == 1 && "Mental Misstep".equals(source.getName())) {
|
dontCounter = true;
|
||||||
dontCounter = false;
|
} else if (tgtCMC == 3 && !MyRandom.percentTrue(ctrChanceCMC3)) {
|
||||||
}
|
dontCounter = true;
|
||||||
|
}
|
||||||
if (dontCounter) {
|
|
||||||
return false;
|
if (tgtSA != null && tgtCMC < aic.getIntProperty(AiProps.MIN_SPELL_CMC_TO_COUNTER)) {
|
||||||
}
|
dontCounter = true;
|
||||||
}
|
Card tgtSource = tgtSA.getHostCard();
|
||||||
|
if ((tgtSource != null && tgtCMC == 0 && tgtSource.isPermanent() && !tgtSource.getManaAbilities().isEmpty() && ctrCmc0ManaPerms)
|
||||||
return toReturn;
|
|| (tgtSA.getApi() == ApiType.DealDamage || tgtSA.getApi() == ApiType.LoseLife || tgtSA.getApi() == ApiType.DamageAll && ctrDamageSpells)
|
||||||
}
|
|| (tgtSA.getApi() == ApiType.Counter && ctrOtherCounters)
|
||||||
|
|| ((tgtSA.getApi() == ApiType.Pump || tgtSA.getApi() == ApiType.PumpAll) && ctrPumpSpells)
|
||||||
@Override
|
|| (tgtSA.getApi() == ApiType.Attach && ctrAuraSpells)
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
|| (tgtSA.getApi() == ApiType.Destroy || tgtSA.getApi() == ApiType.DestroyAll || tgtSA.getApi() == ApiType.Sacrifice
|
||||||
return doTriggerAINoCost(aiPlayer, sa, true);
|
|| tgtSA.getApi() == ApiType.SacrificeAll && ctrRemovalSpells)) {
|
||||||
}
|
dontCounter = false;
|
||||||
|
}
|
||||||
@Override
|
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
if (tgtSource != null && !ctrNamed.isEmpty() && !"none".equalsIgnoreCase(ctrNamed)) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
for (String name : StringUtils.split(ctrNamed, ";")) {
|
||||||
final Game game = ai.getGame();
|
if (name.equals(tgtSource.getName())) {
|
||||||
|
dontCounter = false;
|
||||||
if (tgt != null) {
|
}
|
||||||
if (game.getStack().isEmpty()) {
|
}
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
// should not refrain from countering a CMC X spell if that's the only CMC
|
||||||
sa.resetTargets();
|
// counterable with that particular counterspell type (e.g. Mental Misstep vs. CMC 1 spells)
|
||||||
Pair<SpellAbility, Boolean> pair = chooseTargetSpellAbility(game, sa, ai, mandatory);
|
if (sa.getParamOrDefault("ValidTgts", "").startsWith("Card.cmcEQ")) {
|
||||||
SpellAbility tgtSA = pair.getLeft();
|
int validTgtCMC = AbilityUtils.calculateAmount(source, sa.getParam("ValidTgts").substring(10), sa);
|
||||||
|
if (tgtCMC == validTgtCMC) {
|
||||||
if (tgtSA == null) {
|
dontCounter = false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
sa.getTargets().add(tgtSA);
|
}
|
||||||
if (!mandatory && !pair.getRight()) {
|
|
||||||
// If not mandatory and not preferred, bail out after setting target
|
// Should ALWAYS counter if it doesn't spend a card, otherwise it wastes an opportunity
|
||||||
return false;
|
// to gain card advantage
|
||||||
}
|
if (sa.isAbility()
|
||||||
|
&& (!sa.getPayCosts().hasSpecificCostType(CostDiscard.class))
|
||||||
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
|
&& (!sa.getPayCosts().hasSpecificCostType(CostSacrifice.class))
|
||||||
|
&& (!sa.getPayCosts().hasSpecificCostType(CostExile.class))) {
|
||||||
final Card source = sa.getHostCard();
|
// TODO: maybe also disallow CostPayLife?
|
||||||
if (unlessCost != null) {
|
dontCounter = false;
|
||||||
// Is this Usable Mana Sources? Or Total Available Mana?
|
}
|
||||||
final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size();
|
|
||||||
int toPay = 0;
|
// Null Brooch is special - it has a discard cost, but the AI will be
|
||||||
boolean setPayX = false;
|
// discarding no cards, or is playing a deck where discarding is a benefit
|
||||||
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
// as defined in SpecialCardAi.NullBrooch
|
||||||
setPayX = true;
|
if (sa.hasParam("AILogic")) {
|
||||||
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
if ("NullBrooch".equals(sa.getParam("AILogic"))) {
|
||||||
} else {
|
dontCounter = false;
|
||||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mandatory) {
|
if (dontCounter) {
|
||||||
if (toPay == 0) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
return toReturn;
|
||||||
if (toPay <= usableManaSources) {
|
}
|
||||||
// If this is a reusable Resource, feel free to play it most
|
|
||||||
// of the time
|
@Override
|
||||||
if (!SpellAbilityAi.playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) {
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
return false;
|
return doTriggerAINoCost(aiPlayer, sa, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (setPayX) {
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
source.setSVar("PayX", Integer.toString(toPay));
|
final Game game = ai.getGame();
|
||||||
}
|
|
||||||
}
|
if (tgt != null) {
|
||||||
}
|
if (game.getStack().isEmpty()) {
|
||||||
|
return false;
|
||||||
// TODO Improve AI
|
}
|
||||||
|
|
||||||
// Will return true if this spell can counter (or is Reusable and can
|
sa.resetTargets();
|
||||||
// force the Human into making decisions)
|
Pair<SpellAbility, Boolean> pair = chooseTargetSpellAbility(game, sa, ai, mandatory);
|
||||||
|
SpellAbility tgtSA = pair.getLeft();
|
||||||
// But really it should be more picky about how it counters things
|
|
||||||
return true;
|
if (tgtSA == null) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
public Pair<SpellAbility, Boolean> chooseTargetSpellAbility(Game game, SpellAbility sa, Player ai, boolean mandatory) {
|
sa.getTargets().add(tgtSA);
|
||||||
SpellAbility tgtSA;
|
if (!mandatory && !pair.getRight()) {
|
||||||
SpellAbility leastBadOption = null;
|
// If not mandatory and not preferred, bail out after setting target
|
||||||
SpellAbility bestOption = null;
|
return false;
|
||||||
|
}
|
||||||
Iterator<SpellAbilityStackInstance> it = game.getStack().iterator();
|
|
||||||
SpellAbilityStackInstance si = null;
|
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
|
||||||
while(it.hasNext()) {
|
|
||||||
si = it.next();
|
final Card source = sa.getHostCard();
|
||||||
tgtSA = si.getSpellAbility(true);
|
if (unlessCost != null) {
|
||||||
if (!sa.canTargetSpellAbility(tgtSA)) {
|
Player opp = tgtSA.getActivatingPlayer();
|
||||||
continue;
|
int usableManaSources = ComputerUtilMana.getAvailableManaEstimate(opp);
|
||||||
}
|
|
||||||
if (leastBadOption == null) {
|
int toPay = 0;
|
||||||
leastBadOption = tgtSA;
|
boolean setPayX = false;
|
||||||
}
|
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||||
|
setPayX = true;
|
||||||
if (!CardFactoryUtil.isCounterableBy(tgtSA.getHostCard(), sa) ||
|
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
tgtSA.getActivatingPlayer() == ai ||
|
} else {
|
||||||
!tgtSA.getActivatingPlayer().isOpponentOf(ai)) {
|
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||||
// Is this a "better" least bad option
|
}
|
||||||
if (leastBadOption.getActivatingPlayer().isOpponentOf(ai)) {
|
|
||||||
// NOOP
|
if (!mandatory) {
|
||||||
} else if (sa.getActivatingPlayer().isOpponentOf(ai)) {
|
if (toPay == 0) {
|
||||||
// Target opponents uncounterable stuff, before our own stuff
|
return false;
|
||||||
leastBadOption = tgtSA;
|
}
|
||||||
}
|
|
||||||
continue;
|
if (toPay <= usableManaSources) {
|
||||||
}
|
// If this is a reusable Resource, feel free to play it most
|
||||||
|
// of the time
|
||||||
if (bestOption == null) {
|
if (!SpellAbilityAi.playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) {
|
||||||
bestOption = tgtSA;
|
return false;
|
||||||
} else {
|
}
|
||||||
// TODO Determine if this option is better than the current best option
|
}
|
||||||
boolean betterThanBest = false;
|
}
|
||||||
if (betterThanBest) {
|
|
||||||
bestOption = tgtSA;
|
if (setPayX) {
|
||||||
}
|
source.setSVar("PayX", Integer.toString(toPay));
|
||||||
// Don't really need to keep updating leastBadOption once we have a bestOption
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ImmutablePair<>(bestOption != null ? bestOption : leastBadOption, bestOption != null);
|
// TODO Improve AI
|
||||||
}
|
|
||||||
}
|
// Will return true if this spell can counter (or is Reusable and can
|
||||||
|
// force the Human into making decisions)
|
||||||
|
|
||||||
|
// But really it should be more picky about how it counters things
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pair<SpellAbility, Boolean> chooseTargetSpellAbility(Game game, SpellAbility sa, Player ai, boolean mandatory) {
|
||||||
|
SpellAbility tgtSA;
|
||||||
|
SpellAbility leastBadOption = null;
|
||||||
|
SpellAbility bestOption = null;
|
||||||
|
|
||||||
|
Iterator<SpellAbilityStackInstance> it = game.getStack().iterator();
|
||||||
|
SpellAbilityStackInstance si = null;
|
||||||
|
while(it.hasNext()) {
|
||||||
|
si = it.next();
|
||||||
|
tgtSA = si.getSpellAbility(true);
|
||||||
|
if (!sa.canTargetSpellAbility(tgtSA)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (leastBadOption == null) {
|
||||||
|
leastBadOption = tgtSA;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CardFactoryUtil.isCounterableBy(tgtSA.getHostCard(), sa) ||
|
||||||
|
tgtSA.getActivatingPlayer() == ai ||
|
||||||
|
!tgtSA.getActivatingPlayer().isOpponentOf(ai)) {
|
||||||
|
// Is this a "better" least bad option
|
||||||
|
if (leastBadOption.getActivatingPlayer().isOpponentOf(ai)) {
|
||||||
|
// NOOP
|
||||||
|
} else if (sa.getActivatingPlayer().isOpponentOf(ai)) {
|
||||||
|
// Target opponents uncounterable stuff, before our own stuff
|
||||||
|
leastBadOption = tgtSA;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestOption == null) {
|
||||||
|
bestOption = tgtSA;
|
||||||
|
} else {
|
||||||
|
// TODO Determine if this option is better than the current best option
|
||||||
|
boolean betterThanBest = false;
|
||||||
|
if (betterThanBest) {
|
||||||
|
bestOption = tgtSA;
|
||||||
|
}
|
||||||
|
// Don't really need to keep updating leastBadOption once we have a bestOption
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ImmutablePair<>(bestOption != null ? bestOption : leastBadOption, bestOption != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import forge.game.card.CardCollection;
|
|||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
|
import forge.game.keyword.Keyword;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
|
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ public abstract class CountersAi {
|
|||||||
if (type.equals("M1M1")) {
|
if (type.equals("M1M1")) {
|
||||||
// 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), "Undying");
|
final List<Card> killable = CardLists.getNotKeyword(CardLists.filterToughness(list, amount), Keyword.UNDYING);
|
||||||
if (killable.size() > 0) {
|
if (killable.size() > 0) {
|
||||||
choice = ComputerUtilCard.getBestCreatureAI(killable);
|
choice = ComputerUtilCard.getBestCreatureAI(killable);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,481 +1,490 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import com.google.common.base.Predicate;
|
||||||
import java.util.Map;
|
import com.google.common.collect.Iterables;
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import com.google.common.base.Predicate;
|
import forge.ai.ComputerUtilCard;
|
||||||
import com.google.common.collect.Iterables;
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.Game;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.game.card.*;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.Game;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.card.Card;
|
import forge.game.player.Player;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.game.card.CardUtil;
|
import forge.util.MyRandom;
|
||||||
import forge.game.card.CounterType;
|
import forge.util.collect.FCollection;
|
||||||
import forge.game.phase.PhaseHandler;
|
|
||||||
import forge.game.phase.PhaseType;
|
import java.util.List;
|
||||||
import forge.game.player.Player;
|
import java.util.Map;
|
||||||
import forge.game.spellability.SpellAbility;
|
|
||||||
import forge.game.zone.ZoneType;
|
public class CountersMoveAi extends SpellAbilityAi {
|
||||||
import forge.util.MyRandom;
|
@Override
|
||||||
import forge.util.collect.FCollection;
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
|
|
||||||
public class CountersMoveAi extends SpellAbilityAi {
|
if (sa.usesTargeting()) {
|
||||||
@Override
|
sa.resetTargets();
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
if (!moveTgtAI(ai, sa)) {
|
||||||
|
return false;
|
||||||
if (sa.usesTargeting()) {
|
}
|
||||||
sa.resetTargets();
|
}
|
||||||
if (!moveTgtAI(ai, sa)) {
|
|
||||||
return false;
|
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
return MyRandom.getRandom().nextFloat() < .8f; // random success
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
return MyRandom.getRandom().nextFloat() < .8f; // random success
|
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||||
}
|
final Card host = sa.getHostCard();
|
||||||
|
final String type = sa.getParam("CounterType");
|
||||||
@Override
|
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
||||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
|
||||||
final Card host = sa.getHostCard();
|
// Don't tap creatures that may be able to block
|
||||||
final String type = sa.getParam("CounterType");
|
if (ComputerUtil.waitForBlocking(sa)) {
|
||||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
return false;
|
||||||
|
}
|
||||||
// Don't tap creatures that may be able to block
|
|
||||||
if (ComputerUtil.waitForBlocking(sa)) {
|
if (CounterType.P1P1.equals(cType) && sa.hasParam("Source")) {
|
||||||
return false;
|
int amount = calcAmount(sa, cType);
|
||||||
}
|
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||||
|
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||||
if (CounterType.P1P1.equals(cType) && sa.hasParam("Source")) {
|
// opponent Creature with +1/+1 counter does attack
|
||||||
int amount = calcAmount(sa, cType);
|
// try to steal counter from it to kill it
|
||||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
for (final Card c : srcCards) {
|
||||||
// opponent Creature with +1/+1 counter does attack
|
// source is not controlled by current player
|
||||||
// try to steal counter from it to kill it
|
if (!ph.isPlayerTurn(c.getController())) {
|
||||||
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
continue;
|
||||||
for (final Card c : srcCards) {
|
}
|
||||||
// source is not controlled by current player
|
|
||||||
if (!ph.isPlayerTurn(c.getController())) {
|
int a = c.getCounters(cType);
|
||||||
continue;
|
if (a < amount) {
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
int a = c.getCounters(cType);
|
if (ph.getCombat().isAttacking(c)) {
|
||||||
if (a < amount) {
|
// get copy of creature with removed Counter
|
||||||
continue;
|
final Card cpy = CardUtil.getLKICopy(c);
|
||||||
}
|
// cant use substract on Copy
|
||||||
if (ph.getCombat().isAttacking(c)) {
|
cpy.setCounters(cType, a - amount);
|
||||||
// get copy of creature with removed Counter
|
|
||||||
final Card cpy = CardUtil.getLKICopy(c);
|
// a removed counter would kill it
|
||||||
// cant use substract on Copy
|
if (cpy.getNetToughness() <= cpy.getDamage()) {
|
||||||
cpy.setCounters(cType, a - amount);
|
return true;
|
||||||
|
}
|
||||||
// a removed counter would kill it
|
|
||||||
if (cpy.getNetToughness() <= cpy.getDamage()) {
|
// something you can't block, try to reduce its
|
||||||
return true;
|
// attack
|
||||||
}
|
if (!ComputerUtilCard.canBeBlockedProfitably(ai, cpy)) {
|
||||||
|
return true;
|
||||||
// something you can't block, try to reduce its
|
}
|
||||||
// attack
|
}
|
||||||
if (!ComputerUtilCard.canBeBlockedProfitably(ai, cpy)) {
|
}
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
// for Simic Fluxmage and other
|
||||||
|
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
// for Simic Fluxmage and other
|
|
||||||
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
|
} else if (CounterType.P1P1.equals(cType) && sa.hasParam("Defined")) {
|
||||||
return false;
|
// something like Cyptoplast Root-kin
|
||||||
}
|
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||||
|
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
} else if (CounterType.P1P1.equals(cType) && sa.hasParam("Defined")) {
|
|
||||||
// something like Cyptoplast Root-kin
|
}
|
||||||
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
}
|
||||||
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
// for Simic Fluxmage and other
|
||||||
|
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
// for Simic Fluxmage and other
|
// Make sure that removing the last counter doesn't kill the creature
|
||||||
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
|
if ("Self".equals(sa.getParam("Source"))) {
|
||||||
return false;
|
if (host != null && host.getNetToughness() - 1 <= 0) {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
@Override
|
}
|
||||||
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
|
||||||
|
@Override
|
||||||
if (sa.usesTargeting()) {
|
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
if (!moveTgtAI(ai, sa) && !mandatory) {
|
if (sa.usesTargeting()) {
|
||||||
return false;
|
|
||||||
}
|
if (!moveTgtAI(ai, sa) && !mandatory) {
|
||||||
|
return false;
|
||||||
if (!sa.isTargetNumberValid() && mandatory) {
|
}
|
||||||
final Game game = ai.getGame();
|
|
||||||
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
if (!sa.isTargetNumberValid() && mandatory) {
|
||||||
|
final Game game = ai.getGame();
|
||||||
if (tgtCards.isEmpty()) {
|
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
return false;
|
|
||||||
}
|
if (tgtCards.isEmpty()) {
|
||||||
|
return false;
|
||||||
final Card card = ComputerUtilCard.getWorstAI(tgtCards);
|
}
|
||||||
sa.getTargets().add(card);
|
|
||||||
}
|
final Card card = ComputerUtilCard.getWorstAI(tgtCards);
|
||||||
return true;
|
sa.getTargets().add(card);
|
||||||
} else {
|
}
|
||||||
// no target Probably something like Graft
|
return true;
|
||||||
|
} else {
|
||||||
if (mandatory) {
|
// no target Probably something like Graft
|
||||||
return true;
|
|
||||||
}
|
if (mandatory) {
|
||||||
|
return true;
|
||||||
final Card host = sa.getHostCard();
|
}
|
||||||
|
|
||||||
final String type = sa.getParam("CounterType");
|
final Card host = sa.getHostCard();
|
||||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
|
||||||
|
final String type = sa.getParam("CounterType");
|
||||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
||||||
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
|
|
||||||
|
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||||
if (srcCards.isEmpty() || destCards.isEmpty()) {
|
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
|
||||||
return false;
|
|
||||||
}
|
if (srcCards.isEmpty() || destCards.isEmpty()) {
|
||||||
|
return false;
|
||||||
final Card src = srcCards.get(0);
|
}
|
||||||
final Card dest = destCards.get(0);
|
|
||||||
|
final Card src = srcCards.get(0);
|
||||||
// for such Trigger, do not move counter to another players creature
|
final Card dest = destCards.get(0);
|
||||||
if (!dest.getController().equals(ai)) {
|
|
||||||
return false;
|
// for such Trigger, do not move counter to another players creature
|
||||||
} else if (ComputerUtilCard.isUselessCreature(ai, dest)) {
|
if (!dest.getController().equals(ai)) {
|
||||||
return false;
|
return false;
|
||||||
} else if (dest.hasSVar("EndOfTurnLeavePlay")) {
|
} else if (ComputerUtilCard.isUselessCreature(ai, dest)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
} else if (dest.hasSVar("EndOfTurnLeavePlay")) {
|
||||||
|
return false;
|
||||||
if (cType != null) {
|
}
|
||||||
if (!dest.canReceiveCounters(cType)) {
|
|
||||||
return false;
|
if (cType != null) {
|
||||||
}
|
if (!dest.canReceiveCounters(cType)) {
|
||||||
final int amount = calcAmount(sa, cType);
|
return false;
|
||||||
int a = src.getCounters(cType);
|
}
|
||||||
if (a < amount) {
|
final int amount = calcAmount(sa, cType);
|
||||||
return false;
|
int a = src.getCounters(cType);
|
||||||
}
|
if (a < amount) {
|
||||||
|
return false;
|
||||||
final Card srcCopy = CardUtil.getLKICopy(src);
|
}
|
||||||
// cant use substract on Copy
|
|
||||||
srcCopy.setCounters(cType, a - amount);
|
final Card srcCopy = CardUtil.getLKICopy(src);
|
||||||
|
// cant use substract on Copy
|
||||||
final Card destCopy = CardUtil.getLKICopy(dest);
|
srcCopy.setCounters(cType, a - amount);
|
||||||
destCopy.setCounters(cType, dest.getCounters(cType) + amount);
|
|
||||||
|
final Card destCopy = CardUtil.getLKICopy(dest);
|
||||||
int oldEval = ComputerUtilCard.evaluateCreature(src) + ComputerUtilCard.evaluateCreature(dest);
|
destCopy.setCounters(cType, dest.getCounters(cType) + amount);
|
||||||
int newEval = ComputerUtilCard.evaluateCreature(srcCopy) + ComputerUtilCard.evaluateCreature(destCopy);
|
|
||||||
|
int oldEval = ComputerUtilCard.evaluateCreature(src) + ComputerUtilCard.evaluateCreature(dest);
|
||||||
if (newEval < oldEval) {
|
int newEval = ComputerUtilCard.evaluateCreature(srcCopy) + ComputerUtilCard.evaluateCreature(destCopy);
|
||||||
return false;
|
|
||||||
}
|
if (newEval < oldEval) {
|
||||||
}
|
return false;
|
||||||
// no target
|
}
|
||||||
return true;
|
|
||||||
}
|
// check for some specific AI preferences
|
||||||
}
|
if (src.hasStartOfKeyword("Graft") && "DontMoveCounterIfLethal".equals(src.getSVar("AIGraftPreference"))) {
|
||||||
|
if (cType == CounterType.P1P1 && src.getNetToughness() - src.getTempToughnessBoost() - 1 <= 0) {
|
||||||
@Override
|
return false;
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
}
|
||||||
if (sa.usesTargeting()) {
|
}
|
||||||
sa.resetTargets();
|
}
|
||||||
if (!moveTgtAI(ai, sa)) {
|
// no target
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
@Override
|
||||||
}
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
private static int calcAmount(final SpellAbility sa, final CounterType cType) {
|
sa.resetTargets();
|
||||||
final Card host = sa.getHostCard();
|
if (!moveTgtAI(ai, sa)) {
|
||||||
|
return false;
|
||||||
final String amountStr = sa.getParam("CounterNum");
|
}
|
||||||
|
}
|
||||||
// TODO handle proper calculation of X values based on Cost
|
|
||||||
int amount = 0;
|
return true;
|
||||||
|
}
|
||||||
if (amountStr.equals("All") || amountStr.equals("Any")) {
|
|
||||||
// sa has Source, otherwise Source is the Target
|
private static int calcAmount(final SpellAbility sa, final CounterType cType) {
|
||||||
if (sa.hasParam("Source")) {
|
final Card host = sa.getHostCard();
|
||||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
|
||||||
for (final Card c : srcCards) {
|
final String amountStr = sa.getParam("CounterNum");
|
||||||
amount += c.getCounters(cType);
|
|
||||||
}
|
// TODO handle proper calculation of X values based on Cost
|
||||||
}
|
int amount = 0;
|
||||||
} else {
|
|
||||||
amount = AbilityUtils.calculateAmount(host, amountStr, sa);
|
if (amountStr.equals("All") || amountStr.equals("Any")) {
|
||||||
}
|
// sa has Source, otherwise Source is the Target
|
||||||
return amount;
|
if (sa.hasParam("Source")) {
|
||||||
}
|
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||||
|
for (final Card c : srcCards) {
|
||||||
private boolean moveTgtAI(final Player ai, final SpellAbility sa) {
|
amount += c.getCounters(cType);
|
||||||
|
}
|
||||||
final Card host = sa.getHostCard();
|
}
|
||||||
final Game game = ai.getGame();
|
} else {
|
||||||
final String type = sa.getParam("CounterType");
|
amount = AbilityUtils.calculateAmount(host, amountStr, sa);
|
||||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
}
|
||||||
|
return amount;
|
||||||
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
}
|
||||||
|
|
||||||
if (sa.hasParam("Defined")) {
|
private boolean moveTgtAI(final Player ai, final SpellAbility sa) {
|
||||||
final int amount = calcAmount(sa, cType);
|
|
||||||
tgtCards = CardLists.filter(tgtCards, CardPredicates.hasCounter(cType));
|
final Card host = sa.getHostCard();
|
||||||
|
final Game game = ai.getGame();
|
||||||
// SA uses target for Source
|
final String type = sa.getParam("CounterType");
|
||||||
// Target => Defined
|
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
||||||
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
|
|
||||||
|
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
if (destCards.isEmpty()) {
|
|
||||||
// something went wrong
|
if (sa.hasParam("Defined")) {
|
||||||
return false;
|
final int amount = calcAmount(sa, cType);
|
||||||
}
|
tgtCards = CardLists.filter(tgtCards, CardPredicates.hasCounter(cType));
|
||||||
|
|
||||||
final Card dest = destCards.get(0);
|
// SA uses target for Source
|
||||||
|
// Target => Defined
|
||||||
// remove dest from targets, because move doesn't work that way
|
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
|
||||||
tgtCards.remove(dest);
|
|
||||||
|
if (destCards.isEmpty()) {
|
||||||
if (cType != null && !dest.canReceiveCounters(cType)) {
|
// something went wrong
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// prefered logic for this: try to steal counter
|
final Card dest = destCards.get(0);
|
||||||
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
|
|
||||||
if (!oppList.isEmpty()) {
|
// remove dest from targets, because move doesn't work that way
|
||||||
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
|
tgtCards.remove(dest);
|
||||||
|
|
||||||
@Override
|
if (cType != null && !dest.canReceiveCounters(cType)) {
|
||||||
public boolean apply(Card card) {
|
return false;
|
||||||
// do not weak a useless creature if able
|
}
|
||||||
if (ComputerUtilCard.isUselessCreature(ai, card)) {
|
|
||||||
return false;
|
// prefered logic for this: try to steal counter
|
||||||
}
|
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
|
||||||
|
if (!oppList.isEmpty()) {
|
||||||
final Card srcCardCpy = CardUtil.getLKICopy(card);
|
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
|
||||||
// cant use substract on Copy
|
|
||||||
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
|
@Override
|
||||||
|
public boolean apply(Card card) {
|
||||||
// do not steal a P1P1 from Undying if it would die
|
// do not weak a useless creature if able
|
||||||
// this way
|
if (ComputerUtilCard.isUselessCreature(ai, card)) {
|
||||||
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
return false;
|
||||||
if (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword("Undying") || card.isToken()) {
|
}
|
||||||
return true;
|
|
||||||
}
|
final Card srcCardCpy = CardUtil.getLKICopy(card);
|
||||||
return false;
|
// cant use substract on Copy
|
||||||
}
|
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
|
||||||
return true;
|
|
||||||
}
|
// do not steal a P1P1 from Undying if it would die
|
||||||
|
// this way
|
||||||
});
|
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
||||||
|
if (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken()) {
|
||||||
// if no Prefered found, try normal list
|
return true;
|
||||||
if (best.isEmpty()) {
|
}
|
||||||
best = oppList;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
}
|
||||||
|
|
||||||
if (card != null) {
|
});
|
||||||
sa.getTargets().add(card);
|
|
||||||
return true;
|
// if no Prefered found, try normal list
|
||||||
}
|
if (best.isEmpty()) {
|
||||||
|
best = oppList;
|
||||||
}
|
}
|
||||||
|
|
||||||
// from your creature, try to take from the weakest
|
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
||||||
FCollection<Player> ally = ai.getAllies();
|
|
||||||
ally.add(ai);
|
if (card != null) {
|
||||||
|
sa.getTargets().add(card);
|
||||||
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ally);
|
return true;
|
||||||
if (!aiList.isEmpty()) {
|
}
|
||||||
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
|
|
||||||
|
}
|
||||||
@Override
|
|
||||||
public boolean apply(Card card) {
|
// from your creature, try to take from the weakest
|
||||||
// gain from useless
|
FCollection<Player> ally = ai.getAllies();
|
||||||
if (ComputerUtilCard.isUselessCreature(ai, card)) {
|
ally.add(ai);
|
||||||
return true;
|
|
||||||
}
|
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ally);
|
||||||
|
if (!aiList.isEmpty()) {
|
||||||
// source would leave the game
|
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
|
||||||
if (card.hasSVar("EndOfTurnLeavePlay")) {
|
|
||||||
return true;
|
@Override
|
||||||
}
|
public boolean apply(Card card) {
|
||||||
|
// gain from useless
|
||||||
// try to remove P1P1 from undying or evolve
|
if (ComputerUtilCard.isUselessCreature(ai, card)) {
|
||||||
if (CounterType.P1P1.equals(cType)) {
|
return true;
|
||||||
if (card.hasKeyword("Undying") || card.hasKeyword("Evolve")) {
|
}
|
||||||
return true;
|
|
||||||
}
|
// source would leave the game
|
||||||
}
|
if (card.hasSVar("EndOfTurnLeavePlay")) {
|
||||||
if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) {
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
// try to remove P1P1 from undying or evolve
|
||||||
return false;
|
if (CounterType.P1P1.equals(cType)) {
|
||||||
}
|
if (card.hasKeyword("Undying") || card.hasKeyword("Evolve")) {
|
||||||
});
|
return true;
|
||||||
|
}
|
||||||
if (best.isEmpty()) {
|
}
|
||||||
best = aiList;
|
if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) {
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
Card card = ComputerUtilCard.getWorstCreatureAI(best);
|
|
||||||
|
return false;
|
||||||
if (card != null) {
|
}
|
||||||
sa.getTargets().add(card);
|
});
|
||||||
return true;
|
|
||||||
}
|
if (best.isEmpty()) {
|
||||||
}
|
best = aiList;
|
||||||
|
}
|
||||||
return false;
|
|
||||||
} else {
|
Card card = ComputerUtilCard.getWorstCreatureAI(best);
|
||||||
// SA uses target for Defined
|
|
||||||
// Source => Targeted
|
if (card != null) {
|
||||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
sa.getTargets().add(card);
|
||||||
|
return true;
|
||||||
if (srcCards.isEmpty()) {
|
}
|
||||||
// something went wrong
|
}
|
||||||
return false;
|
|
||||||
}
|
return false;
|
||||||
|
} else {
|
||||||
final Card src = srcCards.get(0);
|
// SA uses target for Defined
|
||||||
if (cType != null) {
|
// Source => Targeted
|
||||||
if (src.getCounters(cType) <= 0) {
|
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||||
return false;
|
|
||||||
}
|
if (srcCards.isEmpty()) {
|
||||||
}
|
// something went wrong
|
||||||
|
return false;
|
||||||
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
|
}
|
||||||
if (!aiList.isEmpty()) {
|
|
||||||
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
|
final Card src = srcCards.get(0);
|
||||||
|
if (cType != null) {
|
||||||
@Override
|
if (src.getCounters(cType) <= 0) {
|
||||||
public boolean apply(Card card) {
|
return false;
|
||||||
// gain from useless
|
}
|
||||||
if (ComputerUtilCard.isUselessCreature(ai, card)) {
|
}
|
||||||
return false;
|
|
||||||
}
|
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
|
||||||
|
if (!aiList.isEmpty()) {
|
||||||
// source would leave the game
|
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
|
||||||
if (card.hasSVar("EndOfTurnLeavePlay")) {
|
|
||||||
return false;
|
@Override
|
||||||
}
|
public boolean apply(Card card) {
|
||||||
|
// gain from useless
|
||||||
if (cType != null) {
|
if (ComputerUtilCard.isUselessCreature(ai, card)) {
|
||||||
if (CounterType.P1P1.equals(cType) && card.hasKeyword("Undying")) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) {
|
// source would leave the game
|
||||||
return false;
|
if (card.hasSVar("EndOfTurnLeavePlay")) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
if (!card.canReceiveCounters(cType)) {
|
|
||||||
return false;
|
if (cType != null) {
|
||||||
}
|
if (CounterType.P1P1.equals(cType) && card.hasKeyword("Undying")) {
|
||||||
}
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) {
|
||||||
});
|
return false;
|
||||||
|
}
|
||||||
if (best.isEmpty()) {
|
|
||||||
best = aiList;
|
if (!card.canReceiveCounters(cType)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
}
|
||||||
|
return false;
|
||||||
if (card != null) {
|
}
|
||||||
sa.getTargets().add(card);
|
});
|
||||||
return true;
|
|
||||||
}
|
if (best.isEmpty()) {
|
||||||
}
|
best = aiList;
|
||||||
|
}
|
||||||
// move counter to opponents creature but only if you can not steal
|
|
||||||
// them
|
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
||||||
// try to move to something useless or something that would leave
|
|
||||||
// play
|
if (card != null) {
|
||||||
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
|
sa.getTargets().add(card);
|
||||||
if (!oppList.isEmpty()) {
|
return true;
|
||||||
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
|
}
|
||||||
|
}
|
||||||
@Override
|
|
||||||
public boolean apply(Card card) {
|
// move counter to opponents creature but only if you can not steal
|
||||||
// gain from useless
|
// them
|
||||||
if (!ComputerUtilCard.isUselessCreature(ai, card)) {
|
// try to move to something useless or something that would leave
|
||||||
return true;
|
// play
|
||||||
}
|
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
|
||||||
|
if (!oppList.isEmpty()) {
|
||||||
// source would leave the game
|
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
|
||||||
if (!card.hasSVar("EndOfTurnLeavePlay")) {
|
|
||||||
return true;
|
@Override
|
||||||
}
|
public boolean apply(Card card) {
|
||||||
|
// gain from useless
|
||||||
return false;
|
if (!ComputerUtilCard.isUselessCreature(ai, card)) {
|
||||||
}
|
return true;
|
||||||
});
|
}
|
||||||
|
|
||||||
if (best.isEmpty()) {
|
// source would leave the game
|
||||||
best = aiList;
|
if (!card.hasSVar("EndOfTurnLeavePlay")) {
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
|
||||||
|
return false;
|
||||||
if (card != null) {
|
}
|
||||||
sa.getTargets().add(card);
|
});
|
||||||
return true;
|
|
||||||
}
|
if (best.isEmpty()) {
|
||||||
}
|
best = aiList;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
}
|
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
||||||
|
|
||||||
// used for multiple sources -> defied
|
if (card != null) {
|
||||||
// or for source -> multiple defined
|
sa.getTargets().add(card);
|
||||||
@Override
|
return true;
|
||||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
}
|
||||||
Player targetedPlayer) {
|
}
|
||||||
if (sa.hasParam("AiLogic")) {
|
return false;
|
||||||
String logic = sa.getParam("AiLogic");
|
}
|
||||||
|
}
|
||||||
if ("ToValid".equals(logic)) {
|
|
||||||
// cards like Forgotten Ancient
|
// used for multiple sources -> defied
|
||||||
// can put counter on any creature, but should only put one on
|
// or for source -> multiple defined
|
||||||
// Ai controlled ones
|
@Override
|
||||||
List<Card> aiCards = CardLists.filterControlledBy(options, ai);
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||||
return ComputerUtilCard.getBestCreatureAI(aiCards);
|
Player targetedPlayer) {
|
||||||
} else if ("FromValid".equals(logic)) {
|
if (sa.hasParam("AiLogic")) {
|
||||||
// cards like Aetherborn Marauder
|
String logic = sa.getParam("AiLogic");
|
||||||
return ComputerUtilCard.getWorstCreatureAI(options);
|
|
||||||
}
|
if ("ToValid".equals(logic)) {
|
||||||
}
|
// cards like Forgotten Ancient
|
||||||
return Iterables.getFirst(options, null);
|
// can put counter on any creature, but should only put one on
|
||||||
}
|
// Ai controlled ones
|
||||||
|
List<Card> aiCards = CardLists.filterControlledBy(options, ai);
|
||||||
// used when selecting how many counters to move
|
return ComputerUtilCard.getBestCreatureAI(aiCards);
|
||||||
@Override
|
} else if ("FromValid".equals(logic)) {
|
||||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
// cards like Aetherborn Marauder
|
||||||
// TODO improve logic behind it
|
return ComputerUtilCard.getWorstCreatureAI(options);
|
||||||
// like keeping the last counter on a 0/0 creature
|
}
|
||||||
return max;
|
}
|
||||||
}
|
return Iterables.getFirst(options, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used when selecting how many counters to move
|
||||||
|
@Override
|
||||||
|
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||||
|
// TODO improve logic behind it
|
||||||
|
// like keeping the last counter on a 0/0 creature
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,98 +1,98 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
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;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
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 CountersProliferateAi extends SpellAbilityAi {
|
public class CountersProliferateAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
|
|
||||||
final List<Card> cperms = Lists.newArrayList();
|
final List<Card> cperms = Lists.newArrayList();
|
||||||
final List<Player> allies = ai.getAllies();
|
final List<Player> allies = ai.getAllies();
|
||||||
allies.add(ai);
|
allies.add(ai);
|
||||||
boolean allyExpOrEnergy = false;
|
boolean allyExpOrEnergy = false;
|
||||||
|
|
||||||
for (final Player p : allies) {
|
for (final Player p : allies) {
|
||||||
// player has experience or energy counter
|
// player has experience or energy counter
|
||||||
if (p.getCounters(CounterType.EXPERIENCE) + p.getCounters(CounterType.ENERGY) >= 1) {
|
if (p.getCounters(CounterType.EXPERIENCE) + p.getCounters(CounterType.ENERGY) >= 1) {
|
||||||
allyExpOrEnergy = true;
|
allyExpOrEnergy = true;
|
||||||
}
|
}
|
||||||
cperms.addAll(CardLists.filter(p.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
cperms.addAll(CardLists.filter(p.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card crd) {
|
public boolean apply(final Card crd) {
|
||||||
if (crd.hasCounters()) {
|
if (crd.hasCounters()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// iterate only over existing counters
|
// iterate only over existing counters
|
||||||
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||||
if (e.getValue() >= 1 && !ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
if (e.getValue() >= 1 && !ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Card> hperms = Lists.newArrayList();
|
final List<Card> hperms = Lists.newArrayList();
|
||||||
boolean opponentPoison = false;
|
boolean opponentPoison = false;
|
||||||
|
|
||||||
for (final Player o : ai.getOpponents()) {
|
for (final Player o : ai.getOpponents()) {
|
||||||
opponentPoison |= o.getPoisonCounters() >= 1;
|
opponentPoison |= o.getPoisonCounters() >= 1;
|
||||||
hperms.addAll(CardLists.filter(o.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
hperms.addAll(CardLists.filter(o.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card crd) {
|
public boolean apply(final Card crd) {
|
||||||
if (crd.hasCounters()) {
|
if (crd.hasCounters()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// iterate only over existing counters
|
// iterate only over existing counters
|
||||||
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||||
if (e.getValue() >= 1 && ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
if (e.getValue() >= 1 && ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (cperms.isEmpty() && hperms.isEmpty() && !opponentPoison && !allyExpOrEnergy) {
|
if (cperms.isEmpty() && hperms.isEmpty() && !opponentPoison && !allyExpOrEnergy) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(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 chance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (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 boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
return canPlayAI(ai, sa);
|
return canPlayAI(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,143 +1,181 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import forge.ai.ComputerUtilCost;
|
import com.google.common.collect.Lists;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.game.card.Card;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.card.Card;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.player.Player;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.MyRandom;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
import java.util.List;
|
import forge.util.MyRandom;
|
||||||
import java.util.Random;
|
|
||||||
|
import java.util.List;
|
||||||
public class CountersPutAllAi extends SpellAbilityAi {
|
|
||||||
@Override
|
public class CountersPutAllAi extends SpellAbilityAi {
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
@Override
|
||||||
// AI needs to be expanded, since this function can be pretty complex
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
// based on what
|
// AI needs to be expanded, since this function can be pretty complex
|
||||||
// the expected targets could be
|
// based on what
|
||||||
final Random r = MyRandom.getRandom();
|
// the expected targets could be
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
List<Card> hList;
|
List<Card> hList;
|
||||||
List<Card> cList;
|
List<Card> cList;
|
||||||
final String type = sa.getParam("CounterType");
|
final String type = sa.getParam("CounterType");
|
||||||
final String amountStr = sa.getParam("CounterNum");
|
final String amountStr = sa.getParam("CounterNum");
|
||||||
final String valid = sa.getParam("ValidCards");
|
final String valid = sa.getParam("ValidCards");
|
||||||
final boolean curse = sa.isCurse();
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final boolean curse = sa.isCurse();
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
hList = CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
|
||||||
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
hList = CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
||||||
|
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
||||||
if (abCost != null) {
|
|
||||||
// AI currently disabled for these costs
|
if (abCost != null) {
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 8, sa)) {
|
// AI currently disabled for these costs
|
||||||
return false;
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 8, sa)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
|
||||||
return false;
|
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
|
||||||
return false;
|
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (tgt != null) {
|
|
||||||
Player pl = curse ? ai.getOpponent() : ai;
|
if (logic.equals("AtEOTOrBlock")) {
|
||||||
sa.getTargets().add(pl);
|
if (!ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && !ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
|
return false;
|
||||||
hList = CardLists.filterControlledBy(hList, pl);
|
}
|
||||||
cList = CardLists.filterControlledBy(cList, pl);
|
} else if (logic.equals("AtOppEOT")) {
|
||||||
}
|
if (!(ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && ai.getGame().getPhaseHandler().getNextTurn() == ai)) {
|
||||||
|
return false;
|
||||||
// TODO improve X value to don't overpay when extra mana won't do
|
}
|
||||||
// anything more useful
|
}
|
||||||
final int amount;
|
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
if (tgt != null) {
|
||||||
// Set PayX here to maximum value.
|
Player pl = curse ? ComputerUtil.getOpponentFor(ai) : ai;
|
||||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
sa.getTargets().add(pl);
|
||||||
source.setSVar("PayX", Integer.toString(amount));
|
|
||||||
} else {
|
hList = CardLists.filterControlledBy(hList, pl);
|
||||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
cList = CardLists.filterControlledBy(cList, pl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
// TODO improve X value to don't overpay when extra mana won't do
|
||||||
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
// anything more useful
|
||||||
|
final int amount;
|
||||||
if (curse) {
|
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
if (type.equals("M1M1")) {
|
// Set PayX here to maximum value.
|
||||||
final List<Card> killable = CardLists.filter(hList, new Predicate<Card>() {
|
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
@Override
|
source.setSVar("PayX", Integer.toString(amount));
|
||||||
public boolean apply(final Card c) {
|
} else {
|
||||||
return c.getNetToughness() <= amount;
|
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
if (!(killable.size() > 2)) {
|
// prevent run-away activations - first time will always return true
|
||||||
return false;
|
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
}
|
|
||||||
} else {
|
if (curse) {
|
||||||
// make sure compy doesn't harm his stuff more than human's
|
if (type.equals("M1M1")) {
|
||||||
// stuff
|
final List<Card> killable = CardLists.filter(hList, new Predicate<Card>() {
|
||||||
if (cList.size() > hList.size()) {
|
@Override
|
||||||
return false;
|
public boolean apply(final Card c) {
|
||||||
}
|
return c.getNetToughness() <= amount;
|
||||||
}
|
}
|
||||||
} else {
|
});
|
||||||
// human has more things that will benefit, don't play
|
if (!(killable.size() > 2)) {
|
||||||
if (hList.size() >= cList.size()) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
} else {
|
||||||
|
// make sure compy doesn't harm his stuff more than human's
|
||||||
//Check for cards that could profit from the ability
|
// stuff
|
||||||
PhaseHandler phase = ai.getGame().getPhaseHandler();
|
if (cList.size() > hList.size()) {
|
||||||
if (type.equals("P1P1") && sa.isAbility() && source.isCreature()
|
return false;
|
||||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
|
}
|
||||||
&& sa instanceof AbilitySub
|
}
|
||||||
&& (!phase.getNextTurn().equals(ai)
|
} else {
|
||||||
|| phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
|
// human has more things that will benefit, don't play
|
||||||
boolean combatants = false;
|
if (hList.size() >= cList.size()) {
|
||||||
for (Card c : hList) {
|
return false;
|
||||||
if (!c.equals(source) && c.isUntapped()) {
|
}
|
||||||
combatants = true;
|
|
||||||
break;
|
//Check for cards that could profit from the ability
|
||||||
}
|
PhaseHandler phase = ai.getGame().getPhaseHandler();
|
||||||
}
|
if (type.equals("P1P1") && sa.isAbility() && source.isCreature()
|
||||||
if (!combatants) {
|
&& sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
|
||||||
return false;
|
&& sa instanceof AbilitySub
|
||||||
}
|
&& (!phase.getNextTurn().equals(ai)
|
||||||
}
|
|| phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
|
||||||
}
|
boolean combatants = false;
|
||||||
|
for (Card c : hList) {
|
||||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
if (!c.equals(source) && c.isUntapped()) {
|
||||||
return chance;
|
combatants = true;
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
return ((r.nextFloat() < .6667) && chance);
|
}
|
||||||
}
|
if (!combatants) {
|
||||||
|
return false;
|
||||||
@Override
|
}
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
}
|
||||||
return canPlayAI(ai, sa);
|
}
|
||||||
}
|
|
||||||
/* (non-Javadoc)
|
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
return chance;
|
||||||
*/
|
}
|
||||||
@Override
|
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
return ((MyRandom.getRandom().nextFloat() < .6667) && chance);
|
||||||
return player.getCreaturesInPlay().size() >= player.getOpponent().getCreaturesInPlay().size();
|
}
|
||||||
}
|
|
||||||
}
|
@Override
|
||||||
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
|
return canPlayAI(ai, sa);
|
||||||
|
}
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
|
return player.getCreaturesInPlay().size() >= ComputerUtil.getOpponentFor(player).getCreaturesInPlay().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
|
List<Player> players = Lists.newArrayList();
|
||||||
|
if (!sa.isCurse()) {
|
||||||
|
players.add(aiPlayer);
|
||||||
|
}
|
||||||
|
players.addAll(aiPlayer.getOpponents());
|
||||||
|
players.addAll(aiPlayer.getAllies());
|
||||||
|
if (sa.isCurse()) {
|
||||||
|
players.add(aiPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final Player p : players) {
|
||||||
|
if (p.canBeTargetedBy(sa) && sa.canTarget(p)) {
|
||||||
|
boolean preferred = false;
|
||||||
|
preferred = (sa.isCurse() && p.isOpponentOf(aiPlayer)) || (!sa.isCurse() && p == aiPlayer);
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(p);
|
||||||
|
return preferred || mandatory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mandatory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,297 +1,287 @@
|
|||||||
/*
|
/*
|
||||||
* Forge: Play Magic: the Gathering.
|
* Forge: Play Magic: the Gathering.
|
||||||
* Copyright (C) 2011 Forge Team
|
* Copyright (C) 2011 Forge Team
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import forge.ai.ComputerUtil;
|
||||||
import java.util.Map;
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.game.Game;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.game.GlobalRuleChange;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.game.card.*;
|
||||||
import forge.game.Game;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.GlobalRuleChange;
|
import forge.game.player.Player;
|
||||||
import forge.game.card.Card;
|
import forge.game.player.PlayerController.BinaryChoiceType;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.card.CardPredicates;
|
import java.util.List;
|
||||||
import forge.game.card.CounterType;
|
import java.util.Map;
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.player.PlayerController.BinaryChoiceType;
|
/**
|
||||||
import forge.game.spellability.SpellAbility;
|
* <p>
|
||||||
import forge.game.spellability.TargetRestrictions;
|
* AbilityFactory_PutOrRemoveCountersAi class.
|
||||||
|
* </p>
|
||||||
/**
|
*
|
||||||
* <p>
|
* @author Forge
|
||||||
* AbilityFactory_PutOrRemoveCountersAi class.
|
* @version $Id$
|
||||||
* </p>
|
*/
|
||||||
*
|
public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||||
* @author Forge
|
|
||||||
* @version $Id$
|
/*
|
||||||
*/
|
* (non-Javadoc)
|
||||||
public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
*
|
||||||
|
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||||
/*
|
* forge.game.spellability.SpellAbility)
|
||||||
* (non-Javadoc)
|
*/
|
||||||
*
|
@Override
|
||||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
* forge.game.spellability.SpellAbility)
|
if (sa.usesTargeting()) {
|
||||||
*/
|
return doTgt(ai, sa, false);
|
||||||
@Override
|
}
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
return super.checkApiLogic(ai, sa);
|
||||||
if (sa.usesTargeting()) {
|
}
|
||||||
return doTgt(ai, sa, false);
|
|
||||||
}
|
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
return super.checkApiLogic(ai, sa);
|
final Game game = ai.getGame();
|
||||||
}
|
|
||||||
|
final int amount = Integer.valueOf(sa.getParam("CounterNum"));
|
||||||
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
|
|
||||||
final Card source = sa.getHostCard();
|
// remove counter with Time might use Exile Zone too
|
||||||
final Game game = ai.getGame();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
// need to targetable
|
||||||
final int amount = Integer.valueOf(sa.getParam("CounterNum"));
|
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(tgt.getZone()), sa);
|
||||||
|
|
||||||
// remove counter with Time might use Exile Zone too
|
if (list.isEmpty()) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
return false;
|
||||||
// need to targetable
|
}
|
||||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(tgt.getZone()), sa);
|
|
||||||
|
// Filter AI-specific targets if provided
|
||||||
if (list.isEmpty()) {
|
list = ComputerUtil.filterAITgts(sa, ai, list, false);
|
||||||
return false;
|
|
||||||
}
|
if (sa.hasParam("CounterType")) {
|
||||||
|
// currently only Jhoira's Timebug
|
||||||
if (sa.hasParam("AITgts")) {
|
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
|
||||||
String aiTgts = sa.getParam("AITgts");
|
|
||||||
CardCollection prefList = CardLists.getValidCards(list, aiTgts.split(","), ai, source, sa);
|
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounter(type, amount));
|
||||||
if (!prefList.isEmpty() || sa.hasParam("AITgtsStrict")) {
|
|
||||||
list = prefList;
|
if (countersList.isEmpty()) {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("CounterType")) {
|
// currently can only target cards you control or you own
|
||||||
// currently only Jhoira's Timebug
|
final Card best = ComputerUtilCard.getBestAI(countersList);
|
||||||
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
|
|
||||||
|
// currently both cards only has one target
|
||||||
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounter(type, amount));
|
sa.getTargets().add(best);
|
||||||
|
return true;
|
||||||
if (countersList.isEmpty()) {
|
} else {
|
||||||
return false;
|
// currently only Clockspinning
|
||||||
}
|
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||||
|
|
||||||
// currently can only target cards you control or you own
|
// logic to remove some counter
|
||||||
final Card best = ComputerUtilCard.getBestAI(countersList);
|
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounters());
|
||||||
|
|
||||||
// currently both cards only has one target
|
if (!countersList.isEmpty()) {
|
||||||
sa.getTargets().add(best);
|
|
||||||
return true;
|
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||||
} else {
|
CardCollectionView depthsList = CardLists.filter(countersList,
|
||||||
// currently only Clockspinning
|
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterType.ICE));
|
||||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
|
||||||
|
if (!depthsList.isEmpty()) {
|
||||||
// logic to remove some counter
|
sa.getTargets().add(depthsList.getFirst());
|
||||||
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounters());
|
return true;
|
||||||
|
}
|
||||||
if (!countersList.isEmpty()) {
|
}
|
||||||
|
|
||||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
// Get rid of Planeswalkers, currently only if it can kill them
|
||||||
CardCollectionView depthsList = CardLists.filter(countersList,
|
// with one touch
|
||||||
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterType.ICE));
|
CardCollection planeswalkerList = CardLists.filter(
|
||||||
|
CardLists.filterControlledBy(countersList, ai.getOpponents()),
|
||||||
if (!depthsList.isEmpty()) {
|
CardPredicates.Presets.PLANESWALKERS,
|
||||||
sa.getTargets().add(depthsList.getFirst());
|
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
|
||||||
return true;
|
|
||||||
}
|
if (!planeswalkerList.isEmpty()) {
|
||||||
}
|
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
||||||
|
return true;
|
||||||
// Get rid of Planeswalkers, currently only if it can kill them
|
}
|
||||||
// with one touch
|
|
||||||
CardCollection planeswalkerList = CardLists.filter(
|
// do as M1M1 part
|
||||||
CardLists.filterControlledBy(countersList, ai.getOpponents()),
|
CardCollection aiList = CardLists.filterControlledBy(countersList, ai);
|
||||||
CardPredicates.Presets.PLANEWALKERS,
|
|
||||||
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
|
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
|
||||||
|
|
||||||
if (!planeswalkerList.isEmpty()) {
|
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, Keyword.PERSIST);
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
if (!aiPersistList.isEmpty()) {
|
||||||
return true;
|
aiM1M1List = aiPersistList;
|
||||||
}
|
}
|
||||||
|
|
||||||
// do as M1M1 part
|
if (!aiM1M1List.isEmpty()) {
|
||||||
CardCollection aiList = CardLists.filterControlledBy(countersList, ai);
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
|
||||||
|
return true;
|
||||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
|
}
|
||||||
|
|
||||||
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, "Persist");
|
// do as P1P1 part
|
||||||
if (!aiPersistList.isEmpty()) {
|
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
|
||||||
aiM1M1List = aiPersistList;
|
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, Keyword.UNDYING);
|
||||||
}
|
|
||||||
|
if (!aiUndyingList.isEmpty()) {
|
||||||
if (!aiM1M1List.isEmpty()) {
|
aiP1P1List = aiUndyingList;
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
|
}
|
||||||
return true;
|
if (!aiP1P1List.isEmpty()) {
|
||||||
}
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
|
||||||
|
return true;
|
||||||
// do as P1P1 part
|
}
|
||||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
|
|
||||||
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, "Undying");
|
// fallback to remove any counter from opponent
|
||||||
|
CardCollection oppList = CardLists.filterControlledBy(countersList, ai.getOpponents());
|
||||||
if (!aiUndyingList.isEmpty()) {
|
oppList = CardLists.filter(oppList, CardPredicates.hasCounters());
|
||||||
aiP1P1List = aiUndyingList;
|
if (!oppList.isEmpty()) {
|
||||||
}
|
final Card best = ComputerUtilCard.getBestAI(oppList);
|
||||||
if (!aiP1P1List.isEmpty()) {
|
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
|
for (final CounterType aType : best.getCounters().keySet()) {
|
||||||
return true;
|
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
||||||
}
|
sa.getTargets().add(best);
|
||||||
|
return true;
|
||||||
// fallback to remove any counter from opponent
|
} else if (!ComputerUtil.isUselessCounter(aType)) {
|
||||||
CardCollection oppList = CardLists.filterControlledBy(countersList, ai.getOpponents());
|
// whould remove positive counter
|
||||||
oppList = CardLists.filter(oppList, CardPredicates.hasCounters());
|
if (best.getCounters(aType) <= amount) {
|
||||||
if (!oppList.isEmpty()) {
|
sa.getTargets().add(best);
|
||||||
final Card best = ComputerUtilCard.getBestAI(oppList);
|
return true;
|
||||||
|
}
|
||||||
for (final CounterType aType : best.getCounters().keySet()) {
|
}
|
||||||
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
}
|
||||||
sa.getTargets().add(best);
|
}
|
||||||
return true;
|
}
|
||||||
} else if (!ComputerUtil.isUselessCounter(aType)) {
|
}
|
||||||
// whould remove positive counter
|
|
||||||
if (best.getCounters(aType) <= amount) {
|
if (mandatory) {
|
||||||
sa.getTargets().add(best);
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
@Override
|
||||||
}
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
return doTgt(ai, sa, true);
|
||||||
if (mandatory) {
|
}
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
|
||||||
return true;
|
/*
|
||||||
}
|
* (non-Javadoc)
|
||||||
return false;
|
*
|
||||||
}
|
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
|
||||||
|
* forge.game.spellability.SpellAbility, java.util.Map)
|
||||||
@Override
|
*/
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
@Override
|
||||||
return doTgt(ai, sa, true);
|
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
||||||
}
|
|
||||||
|
if (options.size() > 1) {
|
||||||
/*
|
final Player ai = sa.getActivatingPlayer();
|
||||||
* (non-Javadoc)
|
final Game game = ai.getGame();
|
||||||
*
|
|
||||||
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
|
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||||
* forge.game.spellability.SpellAbility, java.util.Map)
|
|
||||||
*/
|
Card tgt = (Card) params.get("Target");
|
||||||
@Override
|
|
||||||
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
// planeswalker has high priority for loyalty counters
|
||||||
|
if (tgt.isPlaneswalker() && options.contains(CounterType.LOYALTY)) {
|
||||||
if (options.size() > 1) {
|
return CounterType.LOYALTY;
|
||||||
final Player ai = sa.getActivatingPlayer();
|
}
|
||||||
final Game game = ai.getGame();
|
|
||||||
|
if (tgt.getController().isOpponentOf(ai)) {
|
||||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
// creatures with BaseToughness below or equal zero might be
|
||||||
|
// killed if their counters are removed
|
||||||
Card tgt = (Card) params.get("Target");
|
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
|
||||||
|
if (options.contains(CounterType.P1P1)) {
|
||||||
// planeswalker has high priority for loyalty counters
|
return CounterType.P1P1;
|
||||||
if (tgt.isPlaneswalker() && options.contains(CounterType.LOYALTY)) {
|
} else if (options.contains(CounterType.M1M1)) {
|
||||||
return CounterType.LOYALTY;
|
return CounterType.M1M1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (tgt.getController().isOpponentOf(ai)) {
|
|
||||||
// creatures with BaseToughness below or equal zero might be
|
// fallback logic, select positive counter to remove it
|
||||||
// killed if their counters are removed
|
for (final CounterType type : options) {
|
||||||
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
|
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
|
||||||
if (options.contains(CounterType.P1P1)) {
|
return type;
|
||||||
return CounterType.P1P1;
|
}
|
||||||
} else if (options.contains(CounterType.M1M1)) {
|
}
|
||||||
return CounterType.M1M1;
|
} else {
|
||||||
}
|
// this counters are treat first to be removed
|
||||||
}
|
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.ICE)) {
|
||||||
|
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||||
// fallback logic, select positive counter to remove it
|
return CounterType.ICE;
|
||||||
for (final CounterType type : options) {
|
}
|
||||||
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
|
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.P1P1)) {
|
||||||
return type;
|
return CounterType.P1P1;
|
||||||
}
|
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.M1M1)) {
|
||||||
}
|
return CounterType.M1M1;
|
||||||
} else {
|
}
|
||||||
// this counters are treat first to be removed
|
|
||||||
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.ICE)) {
|
// fallback logic, select positive counter to add more
|
||||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
for (final CounterType type : options) {
|
||||||
return CounterType.ICE;
|
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
|
||||||
}
|
return type;
|
||||||
} else if (tgt.hasKeyword("Undying") && options.contains(CounterType.P1P1)) {
|
}
|
||||||
return CounterType.P1P1;
|
}
|
||||||
} else if (tgt.hasKeyword("Persist") && options.contains(CounterType.M1M1)) {
|
}
|
||||||
return CounterType.M1M1;
|
}
|
||||||
}
|
|
||||||
|
return super.chooseCounterType(options, sa, params);
|
||||||
// fallback logic, select positive counter to add more
|
}
|
||||||
for (final CounterType type : options) {
|
|
||||||
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
|
/*
|
||||||
return type;
|
* (non-Javadoc)
|
||||||
}
|
*
|
||||||
}
|
* @see
|
||||||
}
|
* forge.ai.SpellAbilityAi#chooseBinary(forge.game.player.PlayerController.
|
||||||
}
|
* BinaryChoiceType, forge.game.spellability.SpellAbility, java.util.Map)
|
||||||
|
*/
|
||||||
return super.chooseCounterType(options, sa, params);
|
@Override
|
||||||
}
|
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
|
||||||
|
if (kindOfChoice.equals(BinaryChoiceType.AddOrRemove)) {
|
||||||
/*
|
final Player ai = sa.getActivatingPlayer();
|
||||||
* (non-Javadoc)
|
final Game game = ai.getGame();
|
||||||
*
|
Card tgt = (Card) params.get("Target");
|
||||||
* @see
|
CounterType type = (CounterType) params.get("CounterType");
|
||||||
* forge.ai.SpellAbilityAi#chooseBinary(forge.game.player.PlayerController.
|
|
||||||
* BinaryChoiceType, forge.game.spellability.SpellAbility, java.util.Map)
|
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||||
*/
|
|
||||||
@Override
|
if (tgt.getController().isOpponentOf(ai)) {
|
||||||
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
|
if (type.equals(CounterType.LOYALTY) && tgt.isPlaneswalker()) {
|
||||||
if (kindOfChoice.equals(BinaryChoiceType.AddOrRemove)) {
|
return false;
|
||||||
final Player ai = sa.getActivatingPlayer();
|
}
|
||||||
final Game game = ai.getGame();
|
|
||||||
Card tgt = (Card) params.get("Target");
|
return ComputerUtil.isNegativeCounter(type, tgt);
|
||||||
CounterType type = (CounterType) params.get("CounterType");
|
} else {
|
||||||
|
if (type.equals(CounterType.ICE) && "Dark Depths".equals(tgt.getName())) {
|
||||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||||
|
return false;
|
||||||
if (tgt.getController().isOpponentOf(ai)) {
|
}
|
||||||
if (type.equals(CounterType.LOYALTY) && tgt.isPlaneswalker()) {
|
} else if (type.equals(CounterType.M1M1) && tgt.hasKeyword(Keyword.PERSIST)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
} else if (type.equals(CounterType.P1P1) && tgt.hasKeyword(Keyword.UNDYING)) {
|
||||||
|
return false;
|
||||||
return ComputerUtil.isNegativeCounter(type, tgt);
|
}
|
||||||
} else {
|
|
||||||
if (type.equals(CounterType.ICE) && "Dark Depths".equals(tgt.getName())) {
|
return !ComputerUtil.isNegativeCounter(type, tgt);
|
||||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
}
|
||||||
return false;
|
}
|
||||||
}
|
return super.chooseBinary(kindOfChoice, sa, params);
|
||||||
} else if (type.equals(CounterType.M1M1) && tgt.hasKeyword("Persist")) {
|
}
|
||||||
return false;
|
|
||||||
} else if (type.equals(CounterType.P1P1) && tgt.hasKeyword("Undying")) {
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !ComputerUtil.isNegativeCounter(type, tgt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.chooseBinary(kindOfChoice, sa, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,382 +1,375 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import com.google.common.base.Predicates;
|
||||||
import java.util.Map;
|
import forge.ai.ComputerUtil;
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.game.Game;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.game.GlobalRuleChange;
|
||||||
import forge.game.Game;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.GlobalRuleChange;
|
import forge.game.card.*;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.card.Card;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.player.Player;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.game.phase.PhaseHandler;
|
|
||||||
import forge.game.phase.PhaseType;
|
import java.util.List;
|
||||||
import forge.game.player.Player;
|
import java.util.Map;
|
||||||
import forge.game.spellability.SpellAbility;
|
|
||||||
import forge.game.spellability.TargetRestrictions;
|
public class CountersRemoveAi extends SpellAbilityAi {
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
|
/*
|
||||||
public class CountersRemoveAi extends SpellAbilityAi {
|
* (non-Javadoc)
|
||||||
|
*
|
||||||
/*
|
* @see
|
||||||
* (non-Javadoc)
|
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
|
||||||
*
|
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
|
||||||
* @see
|
*/
|
||||||
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
|
@Override
|
||||||
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
|
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
|
||||||
*/
|
final String type = sa.getParam("CounterType");
|
||||||
@Override
|
|
||||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
|
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases") && !type.equals("M1M1")) {
|
||||||
final String type = sa.getParam("CounterType");
|
return false;
|
||||||
|
}
|
||||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases") && !type.equals("M1M1")) {
|
return super.checkPhaseRestrictions(ai, sa, ph);
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
return super.checkPhaseRestrictions(ai, sa, ph);
|
/*
|
||||||
}
|
* (non-Javadoc)
|
||||||
|
*
|
||||||
/*
|
* @see
|
||||||
* (non-Javadoc)
|
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
|
||||||
*
|
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler,
|
||||||
* @see
|
* java.lang.String)
|
||||||
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
|
*/
|
||||||
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler,
|
@Override
|
||||||
* java.lang.String)
|
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
|
||||||
*/
|
if ("EndOfOpponentsTurn".equals(logic)) {
|
||||||
@Override
|
if (!ph.is(PhaseType.END_OF_TURN) || !ph.getNextTurn().equals(ai)) {
|
||||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
|
return false;
|
||||||
if ("EndOfOpponentsTurn".equals(logic)) {
|
}
|
||||||
if (!ph.is(PhaseType.END_OF_TURN) || !ph.getNextTurn().equals(ai)) {
|
}
|
||||||
return false;
|
return super.checkPhaseRestrictions(ai, sa, ph, logic);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return super.checkPhaseRestrictions(ai, sa, ph, logic);
|
/*
|
||||||
}
|
* (non-Javadoc)
|
||||||
|
*
|
||||||
/*
|
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||||
* (non-Javadoc)
|
* forge.game.spellability.SpellAbility)
|
||||||
*
|
*/
|
||||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
@Override
|
||||||
* forge.game.spellability.SpellAbility)
|
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
*/
|
|
||||||
@Override
|
final String type = sa.getParam("CounterType");
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
final String type = sa.getParam("CounterType");
|
return doTgt(ai, sa, false);
|
||||||
|
}
|
||||||
if (sa.usesTargeting()) {
|
|
||||||
return doTgt(ai, sa, false);
|
if (!type.matches("Any") && !type.matches("All")) {
|
||||||
}
|
final int currCounters = sa.getHostCard().getCounters(CounterType.valueOf(type));
|
||||||
|
if (currCounters < 1) {
|
||||||
if (!type.matches("Any") && !type.matches("All")) {
|
return false;
|
||||||
final int currCounters = sa.getHostCard().getCounters(CounterType.valueOf(type));
|
}
|
||||||
if (currCounters < 1) {
|
}
|
||||||
return false;
|
|
||||||
}
|
return super.checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.checkApiLogic(ai, sa);
|
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
}
|
final Card source = sa.getHostCard();
|
||||||
|
final Game game = ai.getGame();
|
||||||
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
|
|
||||||
final Card source = sa.getHostCard();
|
final String type = sa.getParam("CounterType");
|
||||||
final Game game = ai.getGame();
|
final String amountStr = sa.getParam("CounterNum");
|
||||||
|
|
||||||
final String type = sa.getParam("CounterType");
|
// remove counter with Time might use Exile Zone too
|
||||||
final String amountStr = sa.getParam("CounterNum");
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
CardCollection list = new CardCollection(game.getCardsIn(tgt.getZone()));
|
||||||
// remove counter with Time might use Exile Zone too
|
// need to targetable
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
CardCollection list = new CardCollection(game.getCardsIn(tgt.getZone()));
|
|
||||||
// need to targetable
|
if (list.isEmpty()) {
|
||||||
list = CardLists.getTargetableCards(list, sa);
|
return false;
|
||||||
|
}
|
||||||
if (list.isEmpty()) {
|
|
||||||
return false;
|
// Filter AI-specific targets if provided
|
||||||
}
|
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
|
||||||
|
|
||||||
if (sa.hasParam("AITgts")) {
|
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||||
String aiTgts = sa.getParam("AITgts");
|
|
||||||
CardCollection prefList = CardLists.getValidCards(list, aiTgts.split(","), ai, source, sa);
|
if (type.matches("All")) {
|
||||||
if (!prefList.isEmpty() || sa.hasParam("AITgtsStrict")) {
|
// Logic Part for Vampire Hexmage
|
||||||
list = prefList;
|
// Break Dark Depths
|
||||||
}
|
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||||
}
|
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||||
|
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
||||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
CardPredicates.hasCounter(CounterType.ICE, 3));
|
||||||
|
|
||||||
if (type.matches("All")) {
|
if (!depthsList.isEmpty()) {
|
||||||
// Logic Part for Vampire Hexmage
|
sa.getTargets().add(depthsList.getFirst());
|
||||||
// Break Dark Depths
|
return true;
|
||||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
}
|
||||||
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
}
|
||||||
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
|
||||||
CardPredicates.hasCounter(CounterType.ICE, 3));
|
// Get rid of Planeswalkers:
|
||||||
|
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||||
if (!depthsList.isEmpty()) {
|
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
|
||||||
sa.getTargets().add(depthsList.getFirst());
|
|
||||||
return true;
|
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS,
|
||||||
}
|
CardPredicates.hasCounter(CounterType.LOYALTY, 5));
|
||||||
}
|
|
||||||
|
if (!planeswalkerList.isEmpty()) {
|
||||||
// Get rid of Planeswalkers:
|
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
||||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
return true;
|
||||||
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
|
}
|
||||||
|
|
||||||
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS,
|
} else if (type.matches("Any")) {
|
||||||
CardPredicates.hasCounter(CounterType.LOYALTY, 5));
|
// variable amount for Hex Parasite
|
||||||
|
int amount;
|
||||||
if (!planeswalkerList.isEmpty()) {
|
boolean xPay = false;
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||||
return true;
|
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
}
|
|
||||||
|
if (manaLeft == 0) {
|
||||||
} else if (type.matches("Any")) {
|
return false;
|
||||||
// variable amount for Hex Parasite
|
}
|
||||||
int amount;
|
amount = manaLeft;
|
||||||
boolean xPay = false;
|
xPay = true;
|
||||||
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
} else {
|
||||||
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
|
}
|
||||||
if (manaLeft == 0) {
|
// try to remove them from Dark Depths and Planeswalkers too
|
||||||
return false;
|
|
||||||
}
|
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||||
amount = manaLeft;
|
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||||
xPay = true;
|
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
||||||
} else {
|
CardPredicates.hasCounter(CounterType.ICE));
|
||||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
|
||||||
}
|
if (!depthsList.isEmpty()) {
|
||||||
// try to remove them from Dark Depths and Planeswalkers too
|
Card depth = depthsList.getFirst();
|
||||||
|
int ice = depth.getCounters(CounterType.ICE);
|
||||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
if (amount >= ice) {
|
||||||
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
sa.getTargets().add(depth);
|
||||||
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
if (xPay) {
|
||||||
CardPredicates.hasCounter(CounterType.ICE));
|
source.setSVar("PayX", Integer.toString(ice));
|
||||||
|
}
|
||||||
if (!depthsList.isEmpty()) {
|
return true;
|
||||||
Card depth = depthsList.getFirst();
|
}
|
||||||
int ice = depth.getCounters(CounterType.ICE);
|
}
|
||||||
if (amount >= ice) {
|
}
|
||||||
sa.getTargets().add(depth);
|
|
||||||
if (xPay) {
|
// Get rid of Planeswalkers:
|
||||||
source.setSVar("PayX", Integer.toString(ice));
|
list = game.getPlayers().getCardsIn(ZoneType.Battlefield);
|
||||||
}
|
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
|
||||||
return true;
|
|
||||||
}
|
CardCollection planeswalkerList = CardLists.filter(list,
|
||||||
}
|
Predicates.and(CardPredicates.Presets.PLANESWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
|
||||||
}
|
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
|
||||||
|
|
||||||
// Get rid of Planeswalkers:
|
if (!planeswalkerList.isEmpty()) {
|
||||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
|
||||||
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
|
sa.getTargets().add(best);
|
||||||
|
if (xPay) {
|
||||||
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS,
|
source.setSVar("PayX", Integer.toString(best.getCurrentLoyalty()));
|
||||||
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
|
}
|
||||||
|
return true;
|
||||||
if (!planeswalkerList.isEmpty()) {
|
}
|
||||||
Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
|
|
||||||
sa.getTargets().add(best);
|
// some rules only for amount = 1
|
||||||
if (xPay) {
|
if (!xPay) {
|
||||||
source.setSVar("PayX", Integer.toString(best.getCurrentLoyalty()));
|
// do as M1M1 part
|
||||||
}
|
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||||
return true;
|
|
||||||
}
|
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
|
||||||
|
|
||||||
// some rules only for amount = 1
|
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, Keyword.PERSIST);
|
||||||
if (!xPay) {
|
if (!aiPersistList.isEmpty()) {
|
||||||
// do as M1M1 part
|
aiM1M1List = aiPersistList;
|
||||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
}
|
||||||
|
|
||||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
|
if (!aiM1M1List.isEmpty()) {
|
||||||
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
|
||||||
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, "Persist");
|
return true;
|
||||||
if (!aiPersistList.isEmpty()) {
|
}
|
||||||
aiM1M1List = aiPersistList;
|
|
||||||
}
|
// do as P1P1 part
|
||||||
|
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
|
||||||
if (!aiM1M1List.isEmpty()) {
|
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, Keyword.UNDYING);
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
|
|
||||||
return true;
|
if (!aiUndyingList.isEmpty()) {
|
||||||
}
|
aiP1P1List = aiUndyingList;
|
||||||
|
}
|
||||||
// do as P1P1 part
|
if (!aiP1P1List.isEmpty()) {
|
||||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
|
||||||
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, "Undying");
|
return true;
|
||||||
|
}
|
||||||
if (!aiUndyingList.isEmpty()) {
|
|
||||||
aiP1P1List = aiUndyingList;
|
// fallback to remove any counter from opponent
|
||||||
}
|
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||||
if (!aiP1P1List.isEmpty()) {
|
oppList = CardLists.filter(oppList, CardPredicates.hasCounters());
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
|
if (!oppList.isEmpty()) {
|
||||||
return true;
|
final Card best = ComputerUtilCard.getBestAI(oppList);
|
||||||
}
|
|
||||||
|
for (final CounterType aType : best.getCounters().keySet()) {
|
||||||
// fallback to remove any counter from opponent
|
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
||||||
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
|
sa.getTargets().add(best);
|
||||||
oppList = CardLists.filter(oppList, CardPredicates.hasCounters());
|
return true;
|
||||||
if (!oppList.isEmpty()) {
|
}
|
||||||
final Card best = ComputerUtilCard.getBestAI(oppList);
|
}
|
||||||
|
}
|
||||||
for (final CounterType aType : best.getCounters().keySet()) {
|
}
|
||||||
if (!ComputerUtil.isNegativeCounter(aType, best)) {
|
} else if (type.equals("M1M1")) {
|
||||||
sa.getTargets().add(best);
|
// no special amount for that one yet
|
||||||
return true;
|
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
}
|
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||||
}
|
aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1, amount));
|
||||||
}
|
|
||||||
}
|
CardCollection aiPersist = CardLists.getKeyword(aiList, Keyword.PERSIST);
|
||||||
} else if (type.equals("M1M1")) {
|
if (!aiPersist.isEmpty()) {
|
||||||
// no special amount for that one yet
|
aiList = aiPersist;
|
||||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
}
|
||||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
|
||||||
aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1, amount));
|
// TODO do not remove -1/-1 counters from cards which does need
|
||||||
|
// them for abilities
|
||||||
CardCollection aiPersist = CardLists.getKeyword(aiList, "Persist");
|
|
||||||
if (!aiPersist.isEmpty()) {
|
if (!aiList.isEmpty()) {
|
||||||
aiList = aiPersist;
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
// TODO do not remove -1/-1 counters from cards which does need
|
|
||||||
// them for abilities
|
} else if (type.equals("P1P1")) {
|
||||||
|
// no special amount for that one yet
|
||||||
if (!aiList.isEmpty()) {
|
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
|
||||||
return true;
|
list = CardLists.filter(list, CardPredicates.hasCounter(CounterType.P1P1, amount));
|
||||||
}
|
|
||||||
|
// currently only logic for Bloodcrazed Hoplite, but add logic for
|
||||||
} else if (type.equals("P1P1")) {
|
// targeting ai creatures too
|
||||||
// no special amount for that one yet
|
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
if (!aiList.isEmpty()) {
|
||||||
|
CardCollection aiListUndying = CardLists.getKeyword(aiList, Keyword.UNDYING);
|
||||||
list = CardLists.filter(list, CardPredicates.hasCounter(CounterType.P1P1, amount));
|
if (!aiListUndying.isEmpty()) {
|
||||||
|
aiList = aiListUndying;
|
||||||
// currently only logic for Bloodcrazed Hoplite, but add logic for
|
}
|
||||||
// targeting ai creatures too
|
if (!aiList.isEmpty()) {
|
||||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
||||||
if (!aiList.isEmpty()) {
|
return true;
|
||||||
CardCollection aiListUndying = CardLists.getKeyword(aiList, "Undying");
|
}
|
||||||
if (!aiListUndying.isEmpty()) {
|
}
|
||||||
aiList = aiListUndying;
|
|
||||||
}
|
// need to target opponent creatures
|
||||||
if (!aiList.isEmpty()) {
|
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
|
if (!oppList.isEmpty()) {
|
||||||
return true;
|
CardCollection oppListNotUndying = CardLists.getNotKeyword(oppList, Keyword.UNDYING);
|
||||||
}
|
if (!oppListNotUndying.isEmpty()) {
|
||||||
}
|
oppList = oppListNotUndying;
|
||||||
|
}
|
||||||
// need to target opponent creatures
|
|
||||||
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
|
if (!oppList.isEmpty()) {
|
||||||
if (!oppList.isEmpty()) {
|
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppList));
|
||||||
CardCollection oppListNotUndying = CardLists.getNotKeyword(oppList, "Undying");
|
return true;
|
||||||
if (!oppListNotUndying.isEmpty()) {
|
}
|
||||||
oppList = oppListNotUndying;
|
}
|
||||||
}
|
|
||||||
|
} else if (type.equals("TIME")) {
|
||||||
if (!oppList.isEmpty()) {
|
int amount;
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppList));
|
boolean xPay = false;
|
||||||
return true;
|
// Timecrafting has X R
|
||||||
}
|
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||||
}
|
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
|
|
||||||
} else if (type.equals("TIME")) {
|
if (manaLeft == 0) {
|
||||||
int amount;
|
return false;
|
||||||
boolean xPay = false;
|
}
|
||||||
// Timecrafting has X R
|
amount = manaLeft;
|
||||||
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
xPay = true;
|
||||||
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
} else {
|
||||||
|
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
if (manaLeft == 0) {
|
}
|
||||||
return false;
|
|
||||||
}
|
CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterType.TIME, amount));
|
||||||
amount = manaLeft;
|
|
||||||
xPay = true;
|
if (!timeList.isEmpty()) {
|
||||||
} else {
|
Card best = ComputerUtilCard.getBestAI(timeList);
|
||||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
|
||||||
}
|
int timeCount = best.getCounters(CounterType.TIME);
|
||||||
|
sa.getTargets().add(best);
|
||||||
CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterType.TIME, amount));
|
if (xPay) {
|
||||||
|
source.setSVar("PayX", Integer.toString(timeCount));
|
||||||
if (!timeList.isEmpty()) {
|
}
|
||||||
Card best = ComputerUtilCard.getBestAI(timeList);
|
return true;
|
||||||
|
}
|
||||||
int timeCount = best.getCounters(CounterType.TIME);
|
}
|
||||||
sa.getTargets().add(best);
|
if (mandatory) {
|
||||||
if (xPay) {
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
||||||
source.setSVar("PayX", Integer.toString(timeCount));
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (mandatory) {
|
@Override
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
return true;
|
if (sa.usesTargeting()) {
|
||||||
}
|
return doTgt(aiPlayer, sa, mandatory);
|
||||||
return false;
|
}
|
||||||
}
|
return mandatory;
|
||||||
|
}
|
||||||
@Override
|
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
/*
|
||||||
if (sa.usesTargeting()) {
|
* (non-Javadoc)
|
||||||
return doTgt(aiPlayer, sa, mandatory);
|
*
|
||||||
}
|
* @see forge.ai.SpellAbilityAi#chooseNumber(forge.game.player.Player,
|
||||||
return mandatory;
|
* forge.game.spellability.SpellAbility, int, int, java.util.Map)
|
||||||
}
|
*/
|
||||||
|
@Override
|
||||||
/*
|
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||||
* (non-Javadoc)
|
// TODO Auto-generated method stub
|
||||||
*
|
return super.chooseNumber(player, sa, min, max, params);
|
||||||
* @see forge.ai.SpellAbilityAi#chooseNumber(forge.game.player.Player,
|
}
|
||||||
* forge.game.spellability.SpellAbility, int, int, java.util.Map)
|
|
||||||
*/
|
/*
|
||||||
@Override
|
* (non-Javadoc)
|
||||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
*
|
||||||
// TODO Auto-generated method stub
|
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
|
||||||
return super.chooseNumber(player, sa, min, max, params);
|
* forge.game.spellability.SpellAbility, java.util.Map)
|
||||||
}
|
*/
|
||||||
|
@Override
|
||||||
/*
|
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
||||||
* (non-Javadoc)
|
if (options.size() <= 1) {
|
||||||
*
|
return super.chooseCounterType(options, sa, params);
|
||||||
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
|
}
|
||||||
* forge.game.spellability.SpellAbility, java.util.Map)
|
Player ai = sa.getActivatingPlayer();
|
||||||
*/
|
Card target = (Card) params.get("Target");
|
||||||
@Override
|
|
||||||
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
if (target.getController().isOpponentOf(ai)) {
|
||||||
if (options.size() <= 1) {
|
// if its a Planeswalker try to remove Loyality first
|
||||||
return super.chooseCounterType(options, sa, params);
|
if (target.isPlaneswalker()) {
|
||||||
}
|
return CounterType.LOYALTY;
|
||||||
Player ai = sa.getActivatingPlayer();
|
}
|
||||||
Card target = (Card) params.get("Target");
|
for (CounterType type : options) {
|
||||||
|
if (!ComputerUtil.isNegativeCounter(type, target)) {
|
||||||
if (target.getController().isOpponentOf(ai)) {
|
return type;
|
||||||
// if its a Planeswalker try to remove Loyality first
|
}
|
||||||
if (target.isPlaneswalker()) {
|
}
|
||||||
return CounterType.LOYALTY;
|
} else {
|
||||||
}
|
if (options.contains(CounterType.M1M1) && target.hasKeyword(Keyword.PERSIST)) {
|
||||||
for (CounterType type : options) {
|
return CounterType.M1M1;
|
||||||
if (!ComputerUtil.isNegativeCounter(type, target)) {
|
} else if (options.contains(CounterType.P1P1) && target.hasKeyword(Keyword.UNDYING)) {
|
||||||
return type;
|
return CounterType.M1M1;
|
||||||
}
|
}
|
||||||
}
|
for (CounterType type : options) {
|
||||||
} else {
|
if (ComputerUtil.isNegativeCounter(type, target)) {
|
||||||
if (options.contains(CounterType.M1M1) && target.hasKeyword("Persist")) {
|
return type;
|
||||||
return CounterType.M1M1;
|
}
|
||||||
} else if (options.contains(CounterType.P1P1) && target.hasKeyword("Undying")) {
|
}
|
||||||
return CounterType.M1M1;
|
}
|
||||||
}
|
return super.chooseCounterType(options, sa, params);
|
||||||
for (CounterType type : options) {
|
}
|
||||||
if (ComputerUtil.isNegativeCounter(type, target)) {
|
}
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.chooseCounterType(options, sa, params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,93 +1,168 @@
|
|||||||
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.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.player.Player;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.player.Player;
|
||||||
import forge.util.MyRandom;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.trigger.Trigger;
|
||||||
public abstract class DamageAiBase extends SpellAbilityAi {
|
import forge.game.trigger.TriggerDamageDone;
|
||||||
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
|
import forge.game.zone.ZoneType;
|
||||||
int restDamage = d;
|
import forge.util.MyRandom;
|
||||||
final Game game = comp.getGame();
|
|
||||||
final Player enemy = comp.getOpponent();
|
public abstract class DamageAiBase extends SpellAbilityAi {
|
||||||
if (!sa.canTarget(enemy)) {
|
protected boolean avoidTargetP(final Player comp, final SpellAbility sa) {
|
||||||
return false;
|
Player enemy = ComputerUtil.getOpponentFor(comp);
|
||||||
}
|
// Logic for cards that damage owner, like Fireslinger
|
||||||
if (sa.getTargets() != null && sa.getTargets().getTargets().contains(enemy)) {
|
// Do not target a player if they aren't below 75% of our health.
|
||||||
return false;
|
// Unless Lifelink will cancel the damage to us
|
||||||
}
|
Card hostcard = sa.getHostCard();
|
||||||
|
boolean lifelink = hostcard.hasKeyword(Keyword.LIFELINK);
|
||||||
// burn Planeswalkers
|
if (!lifelink) {
|
||||||
if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) {
|
for (Card ench : hostcard.getEnchantedBy(false)) {
|
||||||
return true;
|
// Treat cards enchanted by older cards with "when enchanted creature deals damage, gain life" as if they had lifelink.
|
||||||
}
|
if (ench.hasSVar("LikeLifeLink")) {
|
||||||
|
if ("True".equals(ench.getSVar("LikeLifeLink"))) {
|
||||||
if (!noPrevention) {
|
lifelink = true;
|
||||||
restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, sa.getHostCard(), false);
|
}
|
||||||
} else {
|
}
|
||||||
restDamage = enemy.staticReplaceDamage(restDamage, sa.getHostCard(), false);
|
}
|
||||||
}
|
}
|
||||||
|
if ("SelfDamage".equals(sa.getParam("AILogic"))) {
|
||||||
if (restDamage == 0) {
|
if (comp.getLife() * 0.75 < enemy.getLife()) {
|
||||||
return false;
|
if (!lifelink) {
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
if (!enemy.canLoseLife()) {
|
}
|
||||||
return false;
|
}
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand);
|
|
||||||
|
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
|
||||||
if ((enemy.getLife() - restDamage) < 5) {
|
// TODO: once the "planeswalker redirection" rule is removed completely, just remove this code and
|
||||||
// drop the human to less than 5
|
// remove the "burn Planeswalkers" code in the called method.
|
||||||
// life
|
return shouldTgtP(comp, sa, d, noPrevention, false);
|
||||||
return true;
|
}
|
||||||
}
|
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention, final boolean noPlaneswalkerRedirection) {
|
||||||
|
int restDamage = d;
|
||||||
if (sa.isSpell()) {
|
final Game game = comp.getGame();
|
||||||
PhaseHandler phase = game.getPhaseHandler();
|
Player enemy = ComputerUtil.getOpponentFor(comp);
|
||||||
// If this is a spell, cast it instead of discarding
|
boolean dmgByCardsInHand = false;
|
||||||
if ((phase.is(PhaseType.END_OF_TURN) || phase.is(PhaseType.MAIN2))
|
|
||||||
&& phase.isPlayerTurn(comp) && (hand.size() > comp.getMaxHandSize())) {
|
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&
|
||||||
return true;
|
sa.getHostCard().getSVar(sa.getParam("NumDmg")).equals("TargetedPlayer$CardsInHand")) {
|
||||||
}
|
dmgByCardsInHand = true;
|
||||||
|
}
|
||||||
// chance to burn player based on current hand size
|
// Not sure if type choice implemented for the AI yet but it should at least recognize this spell hits harder on larger enemy hand size
|
||||||
if (hand.size() > 2) {
|
if ("Blood Oath".equals(sa.getHostCard().getName())) {
|
||||||
float value = 0;
|
dmgByCardsInHand = true;
|
||||||
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
}
|
||||||
//lower chance for sorcery as other spells may be cast in main2
|
|
||||||
if (phase.isPlayerTurn(comp) && phase.is(PhaseType.MAIN2)) {
|
if (!sa.canTarget(enemy)) {
|
||||||
value = 1.0f * restDamage / enemy.getLife();
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
if (sa.getTargets() != null && sa.getTargets().getTargets().contains(enemy)) {
|
||||||
if (phase.isPlayerTurn(enemy) && phase.is(PhaseType.END_OF_TURN)) {
|
return false;
|
||||||
value = 1.5f * restDamage / enemy.getLife();
|
}
|
||||||
}
|
|
||||||
}
|
// Benefits hitting players?
|
||||||
if (value > 0) { //more likely to burn with larger hand
|
// If has triggered ability on dealing damage to an opponent, go for it!
|
||||||
for (int i = 3; i < hand.size(); i++) {
|
Card hostcard = sa.getHostCard();
|
||||||
value *= 1.1f;
|
for (Trigger trig : hostcard.getTriggers()) {
|
||||||
}
|
if (trig instanceof TriggerDamageDone) {
|
||||||
}
|
if (("Opponent".equals(trig.getParam("ValidTarget")))
|
||||||
if (value < 0.2f) { //hard floor to reduce ridiculous odds for instants over time
|
&& (!"True".equals(trig.getParam("CombatDamage")))) {
|
||||||
return false;
|
return true;
|
||||||
} else {
|
}
|
||||||
final float chance = MyRandom.getRandom().nextFloat();
|
}
|
||||||
return chance < value;
|
}
|
||||||
}
|
|
||||||
}
|
// burn Planeswalkers
|
||||||
}
|
// TODO: Must be removed completely when the "planeswalker redirection" rule is removed.
|
||||||
|
if (!noPlaneswalkerRedirection
|
||||||
return false;
|
&& Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) {
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (avoidTargetP(comp, sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!noPrevention) {
|
||||||
|
restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, sa.getHostCard(), false);
|
||||||
|
} else {
|
||||||
|
restDamage = enemy.staticReplaceDamage(restDamage, sa.getHostCard(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restDamage == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enemy.canLoseLife()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand);
|
||||||
|
|
||||||
|
if ((enemy.getLife() - restDamage) < 5) {
|
||||||
|
// drop the human to less than 5
|
||||||
|
// life
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.isSpell()) {
|
||||||
|
PhaseHandler phase = game.getPhaseHandler();
|
||||||
|
// If this is a spell, cast it instead of discarding
|
||||||
|
if ((phase.is(PhaseType.END_OF_TURN) || phase.is(PhaseType.MAIN2))
|
||||||
|
&& phase.isPlayerTurn(comp) && (hand.size() > comp.getMaxHandSize())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// chance to burn player based on current hand size
|
||||||
|
if (hand.size() > 2) {
|
||||||
|
float value = 0;
|
||||||
|
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||||
|
//lower chance for sorcery as other spells may be cast in main2
|
||||||
|
if (phase.isPlayerTurn(comp) && phase.is(PhaseType.MAIN2)) {
|
||||||
|
value = 1.0f * restDamage / enemy.getLife();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If Sudden Impact type spell, and can hit at least 3 cards during draw phase
|
||||||
|
// have a 100% chance to go for it, enemy hand will only lose cards over time!
|
||||||
|
// But if 3 or less cards, use normal rules, just in case enemy starts holding card or plays a draw spell or we need mana for other instants.
|
||||||
|
if (phase.isPlayerTurn(enemy)) {
|
||||||
|
if (dmgByCardsInHand
|
||||||
|
&& (phase.is(PhaseType.DRAW))
|
||||||
|
&& (enemy.getCardsIn(ZoneType.Hand).size() > 3)) {
|
||||||
|
value = 1;
|
||||||
|
} else if (phase.is(PhaseType.END_OF_TURN)
|
||||||
|
|| ((dmgByCardsInHand && phase.getPhase().isAfter(PhaseType.UPKEEP)))) {
|
||||||
|
value = 1.5f * restDamage / enemy.getLife();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value > 0) { //more likely to burn with larger hand
|
||||||
|
for (int i = 3; i < hand.size(); i++) {
|
||||||
|
value *= 1.1f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value < 0.2f) { //hard floor to reduce ridiculous odds for instants over time
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
final float chance = MyRandom.getRandom().nextFloat();
|
||||||
|
return chance < value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,297 +1,330 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import forge.ai.*;
|
||||||
import forge.ai.*;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
import java.util.Random;
|
public class DamageAllAi extends SpellAbilityAi {
|
||||||
|
@Override
|
||||||
public class DamageAllAi extends SpellAbilityAi {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
@Override
|
// AI needs to be expanded, since this function can be pretty complex
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
// based on what the expected targets could be
|
||||||
// AI needs to be expanded, since this function can be pretty complex
|
final Card source = sa.getHostCard();
|
||||||
// based on what the expected targets could be
|
|
||||||
final Card source = sa.getHostCard();
|
// prevent run-away activations - first time will always return true
|
||||||
|
if (MyRandom.getRandom().nextFloat() > Math.pow(.9, sa.getActivationsThisTurn())) {
|
||||||
// prevent run-away activations - first time will always return true
|
return false;
|
||||||
final Random r = MyRandom.getRandom();
|
}
|
||||||
if (r.nextFloat() > Math.pow(.9, sa.getActivationsThisTurn())) {
|
// abCost stuff that should probably be centralized...
|
||||||
return false;
|
final Cost abCost = sa.getPayCosts();
|
||||||
}
|
if (abCost != null) {
|
||||||
// abCost stuff that should probably be centralized...
|
// AI currently disabled for some costs
|
||||||
final Cost abCost = sa.getPayCosts();
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
if (abCost != null) {
|
return false;
|
||||||
// AI currently disabled for some costs
|
}
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
}
|
||||||
return false;
|
// wait until stack is empty (prevents duplicate kills)
|
||||||
}
|
if (!ai.getGame().getStack().isEmpty()) {
|
||||||
}
|
return false;
|
||||||
// wait until stack is empty (prevents duplicate kills)
|
}
|
||||||
if (!ai.getGame().getStack().isEmpty()) {
|
|
||||||
return false;
|
int x = -1;
|
||||||
}
|
final String damage = sa.getParam("NumDmg");
|
||||||
|
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||||
int x = -1;
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$Converge")) {
|
||||||
final String damage = sa.getParam("NumDmg");
|
dmg = ComputerUtilMana.getConvergeCount(sa, ai);
|
||||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
}
|
||||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$Converge")) {
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
dmg = ComputerUtilMana.getConvergeCount(sa, ai);
|
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
}
|
}
|
||||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
if (damage.equals("ChosenX")) {
|
||||||
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
x = source.getCounters(CounterType.LOYALTY);
|
||||||
}
|
}
|
||||||
if (damage.equals("ChosenX")) {
|
if (x == -1) {
|
||||||
x = source.getCounters(CounterType.LOYALTY);
|
Player bestOpp = determineOppToKill(ai, sa, source, dmg);
|
||||||
}
|
if (determineOppToKill(ai, sa, source, dmg) != null) {
|
||||||
if (x == -1) {
|
// we already know we can kill a player, so go for it
|
||||||
return evaluateDamageAll(ai, sa, source, dmg) > 0;
|
return true;
|
||||||
} else {
|
}
|
||||||
int best = -1, best_x = -1;
|
// look for other value in this (damaging creatures or
|
||||||
for (int i = 0; i < x; i++) {
|
// creatures + player, e.g. Pestilence, etc.)
|
||||||
final int value = evaluateDamageAll(ai, sa, source, i);
|
return evaluateDamageAll(ai, sa, source, dmg) > 0;
|
||||||
if (value > best) {
|
} else {
|
||||||
best = value;
|
int best = -1, best_x = -1;
|
||||||
best_x = i;
|
Player bestOpp = determineOppToKill(ai, sa, source, x);
|
||||||
}
|
if (bestOpp != null) {
|
||||||
}
|
// we can finish off a player, so go for it
|
||||||
if (best_x > 0) {
|
|
||||||
if (sa.getSVar(damage).equals("Count$xPaid")) {
|
// TODO: improve this by possibly damaging more creatures
|
||||||
source.setSVar("PayX", Integer.toString(best_x));
|
// on the battlefield belonging to other opponents at the same
|
||||||
}
|
// time, if viable
|
||||||
if (damage.equals("ChosenX")) {
|
best_x = bestOpp.getLife();
|
||||||
source.setSVar("ChosenX", "Number$" + best_x);
|
} else {
|
||||||
}
|
// see if it's possible to get value from killing off creatures
|
||||||
return true;
|
for (int i = 0; i <= x; i++) {
|
||||||
}
|
final int value = evaluateDamageAll(ai, sa, source, i);
|
||||||
return false;
|
if (value > best) {
|
||||||
}
|
best = value;
|
||||||
}
|
best_x = i;
|
||||||
|
}
|
||||||
private int evaluateDamageAll(Player ai, SpellAbility sa, final Card source, int dmg) {
|
}
|
||||||
Player opp = ai.getOpponent();
|
}
|
||||||
final CardCollection humanList = getKillableCreatures(sa, opp, dmg);
|
|
||||||
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
if (best_x > 0) {
|
||||||
|
if (sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
source.setSVar("PayX", Integer.toString(best_x));
|
||||||
if (tgt != null && sa.canTarget(opp)) {
|
}
|
||||||
sa.resetTargets();
|
if (damage.equals("ChosenX")) {
|
||||||
sa.getTargets().add(opp);
|
source.setSVar("ChosenX", "Number$" + best_x);
|
||||||
computerList.clear();
|
}
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
final String validP = sa.hasParam("ValidPlayers") ? sa.getParam("ValidPlayers") : "";
|
return false;
|
||||||
// TODO: if damage is dependant on mana paid, maybe have X be human's max life
|
}
|
||||||
// Don't kill yourself
|
}
|
||||||
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
|
|
||||||
return -1;
|
private Player determineOppToKill(Player ai, SpellAbility sa, Card source, int x) {
|
||||||
}
|
// Attempt to determine which opponent can be finished off such that the most players
|
||||||
|
// are killed at the same time, given X damage tops
|
||||||
// if we can kill human, do it
|
final String validP = sa.hasParam("ValidPlayers") ? sa.getParam("ValidPlayers") : "";
|
||||||
if ((validP.equals("Player") || validP.contains("Opponent"))
|
int aiLife = ai.getLife();
|
||||||
&& (opp.getLife() <= ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) {
|
Player bestOpp = null; // default opponent, if all else fails
|
||||||
return 1;
|
|
||||||
}
|
for (int dmg = 1; dmg <= x; dmg++) {
|
||||||
|
// Don't kill yourself in the process
|
||||||
int minGain = 200; // The minimum gain in destroyed creatures
|
if (validP.equals("Player") && aiLife <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)) {
|
||||||
if (sa.getPayCosts() != null && sa.getPayCosts().isReusuableResource()) {
|
break;
|
||||||
if (computerList.isEmpty()) {
|
}
|
||||||
minGain = 10; // nothing to lose
|
for (Player opp : ai.getOpponents()) {
|
||||||
// no creatures to lose and player can be damaged
|
if ((validP.equals("Player") || validP.contains("Opponent"))
|
||||||
// so do it if it's helping!
|
&& (opp.getLife() <= ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) {
|
||||||
// ----------------------------
|
bestOpp = opp;
|
||||||
// needs future improvement on pestilence :
|
}
|
||||||
// what if we lose creatures but can win by repeated activations?
|
}
|
||||||
// that tactic only works if there are creatures left to keep pestilence in play
|
}
|
||||||
// and can kill the player in a reasonable amount of time (no more than 2-3 turns?)
|
|
||||||
if (validP.equals("Player")) {
|
return bestOpp;
|
||||||
if (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) > 0) {
|
}
|
||||||
// When using Pestilence to hurt players, do it at
|
|
||||||
// the end of the opponent's turn only
|
private int evaluateDamageAll(Player ai, SpellAbility sa, final Card source, int dmg) {
|
||||||
if ((!"DmgAllCreaturesAndPlayers".equals(sa.getParam("AILogic")))
|
final Player opp = ai.getWeakestOpponent();
|
||||||
|| ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)
|
final CardCollection humanList = getKillableCreatures(sa, opp, dmg);
|
||||||
&& (ai.getGame().getNonactivePlayers().contains(ai)))))
|
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
||||||
// Need further improvement : if able to kill immediately with repeated activations, do not wait
|
|
||||||
// for phases! Will also need to implement considering repeated activations for killed creatures!
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
// || (ai.sa.getPayCosts(). ??? )
|
if (tgt != null && sa.canTarget(opp)) {
|
||||||
{
|
sa.resetTargets();
|
||||||
// would take zero damage, and hurt opponent, do it!
|
sa.getTargets().add(opp);
|
||||||
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)<1) {
|
computerList.clear();
|
||||||
return 1;
|
}
|
||||||
}
|
|
||||||
// enemy is expected to die faster than AI from damage if repeated
|
final String validP = sa.hasParam("ValidPlayers") ? sa.getParam("ValidPlayers") : "";
|
||||||
if (ai.getLife() > ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)
|
// TODO: if damage is dependant on mana paid, maybe have X be human's max life
|
||||||
* ((opp.getLife() + ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) - 1)
|
// Don't kill yourself
|
||||||
/ ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) {
|
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
|
||||||
// enemy below 10 life, go for it!
|
return -1;
|
||||||
if ((opp.getLife() < 10)
|
}
|
||||||
&& (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) >= 1)) {
|
|
||||||
return 1;
|
int minGain = 200; // The minimum gain in destroyed creatures
|
||||||
}
|
if (sa.getPayCosts() != null && sa.getPayCosts().isReusuableResource()) {
|
||||||
// At least half enemy remaining life can be removed in one go
|
if (computerList.isEmpty()) {
|
||||||
// worth doing even if enemy still has high health - one more copy of spell to win!
|
minGain = 10; // nothing to lose
|
||||||
if (opp.getLife() <= 2 * ComputerUtilCombat.predictDamageTo(opp, dmg, source, false)) {
|
// no creatures to lose and player can be damaged
|
||||||
return 1;
|
// so do it if it's helping!
|
||||||
}
|
// ----------------------------
|
||||||
}
|
// needs future improvement on pestilence :
|
||||||
}
|
// what if we lose creatures but can win by repeated activations?
|
||||||
}
|
// that tactic only works if there are creatures left to keep pestilence in play
|
||||||
}
|
// and can kill the player in a reasonable amount of time (no more than 2-3 turns?)
|
||||||
} else {
|
if (validP.equals("Player")) {
|
||||||
minGain = 100; // safety for errors in evaluate creature
|
if (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) > 0) {
|
||||||
}
|
// When using Pestilence to hurt players, do it at
|
||||||
} else if (sa.getSubAbility() != null && ai.getGame().getPhaseHandler().isPreCombatMain() && computerList.isEmpty()
|
// the end of the opponent's turn only
|
||||||
&& opp.getCreaturesInPlay().size() > 1 && !ai.getCreaturesInPlay().isEmpty()) {
|
if ((!"DmgAllCreaturesAndPlayers".equals(sa.getParam("AILogic")))
|
||||||
minGain = 126; // prepare for attack
|
|| ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)
|
||||||
}
|
&& (ai.getGame().getNonactivePlayers().contains(ai)))))
|
||||||
|
// Need further improvement : if able to kill immediately with repeated activations, do not wait
|
||||||
return ComputerUtilCard.evaluateCreatureList(humanList) - ComputerUtilCard.evaluateCreatureList(computerList)
|
// for phases! Will also need to implement considering repeated activations for killed creatures!
|
||||||
- minGain;
|
// || (ai.sa.getPayCosts(). ??? )
|
||||||
}
|
{
|
||||||
|
// would take zero damage, and hurt opponent, do it!
|
||||||
@Override
|
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)<1) {
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
return 1;
|
||||||
final Card source = sa.getHostCard();
|
}
|
||||||
String validP = "";
|
// enemy is expected to die faster than AI from damage if repeated
|
||||||
|
if (ai.getLife() > ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)
|
||||||
final String damage = sa.getParam("NumDmg");
|
* ((opp.getLife() + ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) - 1)
|
||||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
/ ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) {
|
||||||
|
// enemy below 10 life, go for it!
|
||||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
if ((opp.getLife() < 10)
|
||||||
// Set PayX here to maximum value.
|
&& (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) >= 1)) {
|
||||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
return 1;
|
||||||
source.setSVar("PayX", Integer.toString(dmg));
|
}
|
||||||
}
|
// At least half enemy remaining life can be removed in one go
|
||||||
|
// worth doing even if enemy still has high health - one more copy of spell to win!
|
||||||
if (sa.hasParam("ValidPlayers")) {
|
if (opp.getLife() <= 2 * ComputerUtilCombat.predictDamageTo(opp, dmg, source, false)) {
|
||||||
validP = sa.getParam("ValidPlayers");
|
return 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Evaluate creatures getting killed
|
}
|
||||||
Player enemy = ai.getOpponent();
|
}
|
||||||
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
|
}
|
||||||
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
} else {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
minGain = 100; // safety for errors in evaluate creature
|
||||||
|
}
|
||||||
if (tgt != null && sa.canTarget(enemy)) {
|
} else if (sa.getSubAbility() != null && ai.getGame().getPhaseHandler().isPreCombatMain() && computerList.isEmpty()
|
||||||
sa.resetTargets();
|
&& opp.getCreaturesInPlay().size() > 1 && !ai.getCreaturesInPlay().isEmpty()) {
|
||||||
sa.getTargets().add(enemy);
|
minGain = 126; // prepare for attack
|
||||||
computerList.clear();
|
}
|
||||||
}
|
|
||||||
// Don't get yourself killed
|
return ComputerUtilCard.evaluateCreatureList(humanList) - ComputerUtilCard.evaluateCreatureList(computerList)
|
||||||
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
|
- minGain;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
// if we can kill human, do it
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
if ((validP.equals("Player") || validP.equals("Opponent") || validP.contains("Targeted"))
|
final Card source = sa.getHostCard();
|
||||||
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
|
String validP = "";
|
||||||
return true;
|
|
||||||
}
|
final String damage = sa.getParam("NumDmg");
|
||||||
|
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||||
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) > ComputerUtilCard
|
|
||||||
.evaluateCreatureList(humanList)) {
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
return false;
|
// Set PayX here to maximum value.
|
||||||
}
|
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
|
source.setSVar("PayX", Integer.toString(dmg));
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
if (sa.hasParam("ValidPlayers")) {
|
||||||
/**
|
validP = sa.getParam("ValidPlayers");
|
||||||
* <p>
|
}
|
||||||
* getKillableCreatures.
|
|
||||||
* </p>
|
// Evaluate creatures getting killed
|
||||||
*
|
Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||||
* @param sa
|
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
||||||
* @param player
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
* a {@link forge.game.player.Player} object.
|
|
||||||
* @param dmg
|
if (tgt != null && sa.canTarget(enemy)) {
|
||||||
* a int.
|
sa.resetTargets();
|
||||||
* @return a {@link forge.game.card.CardCollection} object.
|
sa.getTargets().add(enemy);
|
||||||
*/
|
computerList.clear();
|
||||||
private CardCollection getKillableCreatures(final SpellAbility sa, final Player player, final int dmg) {
|
}
|
||||||
final Card source = sa.getHostCard();
|
// Don't get yourself killed
|
||||||
String validC = sa.hasParam("ValidCards") ? sa.getParam("ValidCards") : "";
|
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
|
||||||
|
return false;
|
||||||
// TODO: X may be something different than X paid
|
}
|
||||||
CardCollection list =
|
|
||||||
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validC.split(","), source.getController(), source, sa);
|
// if we can kill human, do it
|
||||||
|
if ((validP.equals("Player") || validP.equals("Opponent") || validP.contains("Targeted"))
|
||||||
final Predicate<Card> filterKillable = new Predicate<Card>() {
|
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
|
||||||
@Override
|
return true;
|
||||||
public boolean apply(final Card c) {
|
}
|
||||||
return (ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c));
|
|
||||||
}
|
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) > ComputerUtilCard
|
||||||
};
|
.evaluateCreatureList(humanList)) {
|
||||||
|
return false;
|
||||||
list = CardLists.getNotKeyword(list, "Indestructible");
|
}
|
||||||
list = CardLists.filter(list, filterKillable);
|
|
||||||
|
return true;
|
||||||
return list;
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
@Override
|
* <p>
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
* getKillableCreatures.
|
||||||
final Card source = sa.getHostCard();
|
* </p>
|
||||||
String validP = "";
|
*
|
||||||
|
* @param sa
|
||||||
final String damage = sa.getParam("NumDmg");
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
* @param player
|
||||||
|
* a {@link forge.game.player.Player} object.
|
||||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
* @param dmg
|
||||||
// Set PayX here to maximum value.
|
* a int.
|
||||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
* @return a {@link forge.game.card.CardCollection} object.
|
||||||
source.setSVar("PayX", Integer.toString(dmg));
|
*/
|
||||||
}
|
private CardCollection getKillableCreatures(final SpellAbility sa, final Player player, final int dmg) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
if (sa.hasParam("ValidPlayers")) {
|
String validC = sa.hasParam("ValidCards") ? sa.getParam("ValidCards") : "";
|
||||||
validP = sa.getParam("ValidPlayers");
|
|
||||||
}
|
// TODO: X may be something different than X paid
|
||||||
|
CardCollection list =
|
||||||
// Evaluate creatures getting killed
|
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validC.split(","), source.getController(), source, sa);
|
||||||
Player enemy = ai.getOpponent();
|
|
||||||
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
|
final Predicate<Card> filterKillable = new Predicate<Card>() {
|
||||||
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
@Override
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
public boolean apply(final Card c) {
|
||||||
|
return (ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c));
|
||||||
if (tgt != null && sa.canTarget(enemy)) {
|
}
|
||||||
sa.resetTargets();
|
};
|
||||||
sa.getTargets().add(enemy);
|
|
||||||
computerList.clear();
|
list = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
|
||||||
}
|
list = CardLists.filter(list, filterKillable);
|
||||||
|
|
||||||
// If it's not mandatory check a few things
|
return list;
|
||||||
if (mandatory) {
|
}
|
||||||
return true;
|
|
||||||
}
|
@Override
|
||||||
// Don't get yourself killed
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
|
final Card source = sa.getHostCard();
|
||||||
return false;
|
String validP = "";
|
||||||
}
|
|
||||||
|
final String damage = sa.getParam("NumDmg");
|
||||||
// if we can kill human, do it
|
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||||
if ((validP.equals("Player") || validP.contains("Opponent") || validP.contains("Targeted"))
|
|
||||||
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
return true;
|
// Set PayX here to maximum value.
|
||||||
}
|
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
|
source.setSVar("PayX", Integer.toString(dmg));
|
||||||
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) + 50 >= ComputerUtilCard
|
}
|
||||||
.evaluateCreatureList(humanList)) {
|
|
||||||
return false;
|
if (sa.hasParam("ValidPlayers")) {
|
||||||
}
|
validP = sa.getParam("ValidPlayers");
|
||||||
|
}
|
||||||
return true;
|
|
||||||
}
|
// Evaluate creatures getting killed
|
||||||
}
|
Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||||
|
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
|
||||||
|
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
|
||||||
|
if (tgt != null && sa.canTarget(enemy)) {
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(enemy);
|
||||||
|
computerList.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's not mandatory check a few things
|
||||||
|
if (mandatory) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Don't get yourself killed
|
||||||
|
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we can kill human, do it
|
||||||
|
if ((validP.equals("Player") || validP.contains("Opponent") || validP.contains("Targeted"))
|
||||||
|
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) + 50 >= ComputerUtilCard
|
||||||
|
.evaluateCreatureList(humanList)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,58 +1,54 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
import forge.ai.SpecialCardAi;
|
import forge.ai.SpecialCardAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.player.Player;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.player.PlayerCollection;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.player.Player;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.player.PlayerCollection;
|
|
||||||
import forge.game.player.PlayerPredicates;
|
public class DamageEachAi extends DamageAiBase {
|
||||||
import forge.game.spellability.SpellAbility;
|
|
||||||
import forge.game.spellability.TargetRestrictions;
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
public class DamageEachAi extends DamageAiBase {
|
*/
|
||||||
|
@Override
|
||||||
/* (non-Javadoc)
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
*/
|
final String logic = sa.getParam("AILogic");
|
||||||
@Override
|
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
Player weakestOpp = targetableOpps.min(PlayerPredicates.compareByLife());
|
||||||
final String logic = sa.getParam("AILogic");
|
|
||||||
|
if (tgt != null && weakestOpp != null) {
|
||||||
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
sa.resetTargets();
|
||||||
Player weakestOpp = targetableOpps.min(PlayerPredicates.compareByLife());
|
sa.getTargets().add(weakestOpp);
|
||||||
|
}
|
||||||
if (tgt != null && weakestOpp != null) {
|
|
||||||
sa.resetTargets();
|
if ("MadSarkhanUltimate".equals(logic)) {
|
||||||
sa.getTargets().add(weakestOpp);
|
return SpecialCardAi.SarkhanTheMad.considerUltimate(ai, sa, weakestOpp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("MadSarkhanUltimate".equals(logic)) {
|
final String damage = sa.getParam("NumDmg");
|
||||||
return SpecialCardAi.SarkhanTheMad.considerUltimate(ai, sa, weakestOpp);
|
final int iDmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||||
}
|
return this.shouldTgtP(ai, sa, iDmg, false);
|
||||||
|
}
|
||||||
final String damage = sa.getParam("NumDmg");
|
|
||||||
final int iDmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
@Override
|
||||||
return this.shouldTgtP(ai, sa, iDmg, false);
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
}
|
// check AI life before playing this drawback?
|
||||||
|
return true;
|
||||||
@Override
|
}
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
|
||||||
// check AI life before playing this drawback?
|
/* (non-Javadoc)
|
||||||
return true;
|
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||||
}
|
*/
|
||||||
|
@Override
|
||||||
/* (non-Javadoc)
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
|
||||||
*/
|
return canPlayAI(ai, sa);
|
||||||
@Override
|
}
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
|
||||||
|
}
|
||||||
return canPlayAI(ai, sa);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,224 +1,224 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
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.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.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.cost.Cost;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetChoices;
|
import forge.game.spellability.TargetChoices;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class DamagePreventAi extends SpellAbilityAi {
|
public class DamagePreventAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Card hostCard = sa.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();
|
final Cost cost = sa.getPayCosts();
|
||||||
|
|
||||||
// temporarily disabled until better AI
|
// temporarily disabled until better AI
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
|
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard)) {
|
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
||||||
return false;
|
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
|
// As far as I can tell these Defined Cards will only have one of
|
||||||
// them
|
// them
|
||||||
final List<GameObject> objects = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
final List<GameObject> objects = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
// react to threats on the stack
|
// react to threats on the stack
|
||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
|
final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
|
||||||
for (final Object o : objects) {
|
for (final Object o : objects) {
|
||||||
if (threatenedObjects.contains(o)) {
|
if (threatenedObjects.contains(o)) {
|
||||||
chance = true;
|
chance = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
PhaseHandler handler = game.getPhaseHandler();
|
PhaseHandler handler = game.getPhaseHandler();
|
||||||
if (handler.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
if (handler.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
boolean flag = false;
|
boolean flag = false;
|
||||||
for (final Object o : objects) {
|
for (final Object o : objects) {
|
||||||
if (o instanceof Card) {
|
if (o instanceof Card) {
|
||||||
flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, (Card) o, combat);
|
flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, (Card) o, combat);
|
||||||
} else if (o instanceof Player) {
|
} else if (o instanceof Player) {
|
||||||
// Don't need to worry about Combat Damage during AI's turn
|
// Don't need to worry about Combat Damage during AI's turn
|
||||||
final Player p = (Player) o;
|
final Player p = (Player) o;
|
||||||
if (!handler.isPlayerTurn(p)) {
|
if (!handler.isPlayerTurn(p)) {
|
||||||
flag |= (p == ai && ((ComputerUtilCombat.wouldLoseLife(ai, combat) && sa
|
flag |= (p == ai && ((ComputerUtilCombat.wouldLoseLife(ai, combat) && sa
|
||||||
.isAbility()) || ComputerUtilCombat.lifeInDanger(ai, combat)));
|
.isAbility()) || ComputerUtilCombat.lifeInDanger(ai, combat)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // non-targeted
|
} // non-targeted
|
||||||
|
|
||||||
// react to threats on the stack
|
// react to threats on the stack
|
||||||
else if (!game.getStack().isEmpty()) {
|
else if (!game.getStack().isEmpty()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
final TargetChoices tcs = sa.getTargets();
|
final TargetChoices tcs = sa.getTargets();
|
||||||
// check stack for something on the stack will kill anything i control
|
// check stack for something on the stack will kill anything i control
|
||||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
|
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
|
||||||
|
|
||||||
if (objects.contains(ai)) {
|
if (objects.contains(ai)) {
|
||||||
tcs.add(ai);
|
tcs.add(ai);
|
||||||
chance = true;
|
chance = true;
|
||||||
}
|
}
|
||||||
final List<Card> threatenedTargets = new ArrayList<Card>();
|
final List<Card> threatenedTargets = new ArrayList<Card>();
|
||||||
// filter AIs battlefield by what I can target
|
// filter AIs battlefield by what I can target
|
||||||
List<Card> targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, hostCard, sa);
|
List<Card> targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, hostCard, sa);
|
||||||
targetables = CardLists.getTargetableCards(targetables, sa);
|
targetables = CardLists.getTargetableCards(targetables, sa);
|
||||||
|
|
||||||
for (final Card c : targetables) {
|
for (final Card c : targetables) {
|
||||||
if (objects.contains(c)) {
|
if (objects.contains(c)) {
|
||||||
threatenedTargets.add(c);
|
threatenedTargets.add(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!threatenedTargets.isEmpty()) {
|
if (!threatenedTargets.isEmpty()) {
|
||||||
// Choose "best" of the remaining to save
|
// Choose "best" of the remaining to save
|
||||||
tcs.add(ComputerUtilCard.getBestCreatureAI(threatenedTargets));
|
tcs.add(ComputerUtilCard.getBestCreatureAI(threatenedTargets));
|
||||||
chance = true;
|
chance = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // Protect combatants
|
} // Protect combatants
|
||||||
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
final TargetChoices tcs = sa.getTargets();
|
final TargetChoices tcs = sa.getTargets();
|
||||||
if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, combat)
|
if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, combat)
|
||||||
&& (ComputerUtilCombat.lifeInDanger(ai, combat) || sa.isAbility() || sa.isTrigger())
|
&& (ComputerUtilCombat.lifeInDanger(ai, combat) || sa.isAbility() || sa.isTrigger())
|
||||||
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
|
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
|
||||||
tcs.add(ai);
|
tcs.add(ai);
|
||||||
chance = true;
|
chance = true;
|
||||||
} else {
|
} else {
|
||||||
// filter AIs battlefield by what I can target
|
// filter AIs battlefield by what I can target
|
||||||
CardCollectionView targetables = ai.getCardsIn(ZoneType.Battlefield);
|
CardCollectionView targetables = ai.getCardsIn(ZoneType.Battlefield);
|
||||||
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, hostCard, sa);
|
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, hostCard, sa);
|
||||||
targetables = CardLists.getTargetableCards(targetables, sa);
|
targetables = CardLists.getTargetableCards(targetables, sa);
|
||||||
|
|
||||||
if (targetables.isEmpty()) {
|
if (targetables.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final CardCollection combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES);
|
final CardCollection combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES);
|
||||||
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
||||||
|
|
||||||
for (final Card c : combatants) {
|
for (final Card c : combatants) {
|
||||||
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && tcs.getNumTargeted() < tgt.getMaxTargets(hostCard, sa)) {
|
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && tcs.getNumTargeted() < tgt.getMaxTargets(hostCard, sa)) {
|
||||||
tcs.add(c);
|
tcs.add(c);
|
||||||
chance = true;
|
chance = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tgt != null && sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().getTargets().isEmpty()) {
|
if (tgt != null && sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().getTargets().isEmpty()) {
|
||||||
tgt.addDividedAllocation(sa.getTargets().getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
tgt.addDividedAllocation(sa.getTargets().getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||||
}
|
}
|
||||||
|
|
||||||
return chance;
|
return chance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(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) {
|
||||||
// If there's no target on the trigger, just say yes.
|
// If there's no target on the trigger, just say yes.
|
||||||
chance = true;
|
chance = true;
|
||||||
} else {
|
} else {
|
||||||
chance = preventDamageMandatoryTarget(ai, sa, mandatory);
|
chance = preventDamageMandatoryTarget(ai, sa, mandatory);
|
||||||
}
|
}
|
||||||
|
|
||||||
return chance;
|
return chance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* preventDamageMandatoryTarget.
|
* preventDamageMandatoryTarget.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @param mandatory
|
* @param mandatory
|
||||||
* a boolean.
|
* a boolean.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
// filter AIs battlefield by what I can target
|
// filter AIs battlefield by what I can target
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
CardCollectionView targetables = game.getCardsIn(ZoneType.Battlefield);
|
CardCollectionView targetables = game.getCardsIn(ZoneType.Battlefield);
|
||||||
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||||
final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai);
|
final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai);
|
||||||
Card target = null;
|
Card target = null;
|
||||||
|
|
||||||
if (targetables.isEmpty()) {
|
if (targetables.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mandatory && compTargetables.isEmpty()) {
|
if (!mandatory && compTargetables.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!compTargetables.isEmpty()) {
|
if (!compTargetables.isEmpty()) {
|
||||||
final CardCollection combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
|
final CardCollection combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
|
||||||
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
||||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
Combat combat = game.getCombat();
|
Combat combat = game.getCombat();
|
||||||
for (final Card c : combatants) {
|
for (final Card c : combatants) {
|
||||||
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
|
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
|
||||||
target = c;
|
target = c;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
target = combatants.get(0);
|
target = combatants.get(0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
target = ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true);
|
target = ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true);
|
||||||
}
|
}
|
||||||
sa.getTargets().add(target);
|
sa.getTargets().add(target);
|
||||||
if (sa.hasParam("DividedAsYouChoose")) {
|
if (sa.hasParam("DividedAsYouChoose")) {
|
||||||
tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +1,59 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.cost.Cost;
|
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;
|
||||||
|
|
||||||
public class DamagePreventAllAi extends SpellAbilityAi {
|
public class DamagePreventAllAi 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 boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Card hostCard = sa.getHostCard();
|
final Card hostCard = sa.getHostCard();
|
||||||
boolean chance = false;
|
boolean chance = false;
|
||||||
|
|
||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
|
|
||||||
// temporarily disabled until better AI
|
// temporarily disabled until better AI
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
|
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
|
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard)) {
|
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ai.getGame().getStack().isEmpty()) {
|
if (!ai.getGame().getStack().isEmpty()) {
|
||||||
// TODO check stack for something on the stack will kill anything i
|
// TODO check stack for something on the stack will kill anything i
|
||||||
// control
|
// control
|
||||||
|
|
||||||
} // Protect combatants
|
} // Protect combatants
|
||||||
else if (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
else if (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
return chance;
|
return chance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
boolean chance = true;
|
boolean chance = true;
|
||||||
|
|
||||||
return chance;
|
return chance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,282 +1,283 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.game.Game;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.Game;
|
||||||
import forge.game.card.Card;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.player.Player;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
public class DebuffAi extends SpellAbilityAi {
|
|
||||||
// *************************************************************************
|
public class DebuffAi extends SpellAbilityAi {
|
||||||
// ***************************** Debuff ************************************
|
// *************************************************************************
|
||||||
// *************************************************************************
|
// ***************************** Debuff ************************************
|
||||||
|
// *************************************************************************
|
||||||
@Override
|
|
||||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
@Override
|
||||||
// if there is no target and host card isn't in play, don't activate
|
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
// if there is no target and host card isn't in play, don't activate
|
||||||
final Game game = ai.getGame();
|
final Card source = sa.getHostCard();
|
||||||
if ((sa.getTargetRestrictions() == null) && !source.isInPlay()) {
|
final Game game = ai.getGame();
|
||||||
return false;
|
if ((sa.getTargetRestrictions() == null) && !source.isInPlay()) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
final Cost cost = sa.getPayCosts();
|
|
||||||
|
final Cost cost = sa.getPayCosts();
|
||||||
// temporarily disabled until AI is improved
|
|
||||||
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source)) {
|
// temporarily disabled until AI is improved
|
||||||
return false;
|
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source, sa)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 40, sa)) {
|
|
||||||
return false;
|
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 40, sa)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
|
|
||||||
return false;
|
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
|
||||||
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
// Phase Restrictions
|
|
||||||
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
// Phase Restrictions
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| !game.getStack().isEmpty()) {
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
// Instant-speed pumps should not be cast outside of combat when the
|
|| !game.getStack().isEmpty()) {
|
||||||
// stack is empty
|
// Instant-speed pumps should not be cast outside of combat when the
|
||||||
if (!SpellAbilityAi.isSorcerySpeed(sa)) {
|
// stack is empty
|
||||||
return false;
|
if (!SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) {
|
|
||||||
List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) {
|
||||||
|
List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
final Combat combat = game.getCombat();
|
|
||||||
return Iterables.any(cards, new Predicate<Card>() {
|
final Combat combat = game.getCombat();
|
||||||
@Override
|
return Iterables.any(cards, new Predicate<Card>() {
|
||||||
public boolean apply(final Card c) {
|
@Override
|
||||||
|
public boolean apply(final Card c) {
|
||||||
if (c.getController().equals(sa.getActivatingPlayer()) || combat == null)
|
|
||||||
return false;
|
if (c.getController().equals(sa.getActivatingPlayer()) || combat == null)
|
||||||
|
return false;
|
||||||
if (!combat.isBlocking(c) && !combat.isAttacking(c)) {
|
|
||||||
return false;
|
if (!combat.isBlocking(c) && !combat.isAttacking(c)) {
|
||||||
}
|
return false;
|
||||||
// don't add duplicate negative keywords
|
}
|
||||||
return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & ")));
|
// don't add duplicate negative keywords
|
||||||
}
|
return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & ")));
|
||||||
});
|
}
|
||||||
} else {
|
});
|
||||||
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
|
} else {
|
||||||
}
|
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@Override
|
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
@Override
|
||||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be
|
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||||
// here?
|
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be
|
||||||
} else {
|
// here?
|
||||||
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
|
} else {
|
||||||
}
|
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
|
||||||
|
}
|
||||||
return true;
|
|
||||||
} // debuffDrawbackAI()
|
return true;
|
||||||
|
} // debuffDrawbackAI()
|
||||||
/**
|
|
||||||
* <p>
|
/**
|
||||||
* debuffTgtAI.
|
* <p>
|
||||||
* </p>
|
* debuffTgtAI.
|
||||||
*
|
* </p>
|
||||||
* @param sa
|
*
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* @param sa
|
||||||
* @param kws
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* a {@link java.util.ArrayList} object.
|
* @param kws
|
||||||
* @param mandatory
|
* a {@link java.util.ArrayList} object.
|
||||||
* a boolean.
|
* @param mandatory
|
||||||
* @return a boolean.
|
* a boolean.
|
||||||
*/
|
* @return a boolean.
|
||||||
private boolean debuffTgtAI(final Player ai, final SpellAbility sa, final List<String> kws, final boolean mandatory) {
|
*/
|
||||||
// this would be for evasive things like Flying, Unblockable, etc
|
private boolean debuffTgtAI(final Player ai, final SpellAbility sa, final List<String> kws, final boolean mandatory) {
|
||||||
if (!mandatory && ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
// this would be for evasive things like Flying, Unblockable, etc
|
||||||
return false;
|
if (!mandatory && ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
sa.resetTargets();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
CardCollection list = getCurseCreatures(ai, sa, kws == null ? Lists.<String>newArrayList() : kws);
|
sa.resetTargets();
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
CardCollection list = getCurseCreatures(ai, sa, kws == null ? Lists.<String>newArrayList() : kws);
|
||||||
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||||
// several uses here:
|
|
||||||
// 1. make human creatures lose evasion when they are attacking
|
// several uses here:
|
||||||
// 2. make human creatures lose Flying/Horsemanship/Shadow/etc. when
|
// 1. make human creatures lose evasion when they are attacking
|
||||||
// Comp is attacking
|
// 2. make human creatures lose Flying/Horsemanship/Shadow/etc. when
|
||||||
// 3. remove Indestructible keyword so it can be destroyed?
|
// Comp is attacking
|
||||||
// 3a. remove Persist?
|
// 3. remove Indestructible keyword so it can be destroyed?
|
||||||
|
// 3a. remove Persist?
|
||||||
if (list.isEmpty()) {
|
|
||||||
return mandatory && debuffMandatoryTarget(ai, sa, mandatory);
|
if (list.isEmpty()) {
|
||||||
}
|
return mandatory && debuffMandatoryTarget(ai, sa, mandatory);
|
||||||
|
}
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
|
||||||
Card t = null;
|
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
// boolean goodt = false;
|
Card t = null;
|
||||||
|
// boolean goodt = false;
|
||||||
if (list.isEmpty()) {
|
|
||||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
if (list.isEmpty()) {
|
||||||
if (mandatory) {
|
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
||||||
return debuffMandatoryTarget(ai, sa, mandatory);
|
if (mandatory) {
|
||||||
}
|
return debuffMandatoryTarget(ai, sa, mandatory);
|
||||||
|
}
|
||||||
sa.resetTargets();
|
|
||||||
return false;
|
sa.resetTargets();
|
||||||
} else {
|
return false;
|
||||||
// TODO is this good enough? for up to amounts?
|
} else {
|
||||||
break;
|
// TODO is this good enough? for up to amounts?
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
t = ComputerUtilCard.getBestCreatureAI(list);
|
|
||||||
sa.getTargets().add(t);
|
t = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
list.remove(t);
|
sa.getTargets().add(t);
|
||||||
}
|
list.remove(t);
|
||||||
|
}
|
||||||
return true;
|
|
||||||
} // pumpTgtAI()
|
return true;
|
||||||
|
} // pumpTgtAI()
|
||||||
/**
|
|
||||||
* <p>
|
/**
|
||||||
* getCurseCreatures.
|
* <p>
|
||||||
* </p>
|
* getCurseCreatures.
|
||||||
*
|
* </p>
|
||||||
* @param sa
|
*
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* @param sa
|
||||||
* @param kws
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* a {@link java.util.ArrayList} object.
|
* @param kws
|
||||||
* @return a CardCollection.
|
* a {@link java.util.ArrayList} object.
|
||||||
*/
|
* @return a CardCollection.
|
||||||
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
|
*/
|
||||||
final Player opp = ai.getOpponent();
|
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
|
||||||
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
|
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
if (!list.isEmpty()) {
|
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
if (!list.isEmpty()) {
|
||||||
@Override
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
public boolean apply(final Card c) {
|
@Override
|
||||||
return c.hasAnyKeyword(kws); // don't add duplicate negative
|
public boolean apply(final Card c) {
|
||||||
// keywords
|
return c.hasAnyKeyword(kws); // don't add duplicate negative
|
||||||
}
|
// keywords
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
return list;
|
}
|
||||||
} // getCurseCreatures()
|
return list;
|
||||||
|
} // getCurseCreatures()
|
||||||
/**
|
|
||||||
* <p>
|
/**
|
||||||
* debuffMandatoryTarget.
|
* <p>
|
||||||
* </p>
|
* debuffMandatoryTarget.
|
||||||
*
|
* </p>
|
||||||
* @param sa
|
*
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* @param sa
|
||||||
* @param mandatory
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* a boolean.
|
* @param mandatory
|
||||||
* @return a boolean.
|
* a boolean.
|
||||||
*/
|
* @return a boolean.
|
||||||
private boolean debuffMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
*/
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
private boolean debuffMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(),
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(),
|
||||||
|
sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||||
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
|
||||||
sa.resetTargets();
|
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
return false;
|
sa.resetTargets();
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
// Remove anything that's already been targeted
|
|
||||||
for (final Card c : sa.getTargets().getTargetCards()) {
|
// Remove anything that's already been targeted
|
||||||
list.remove(c);
|
for (final Card c : sa.getTargets().getTargetCards()) {
|
||||||
}
|
list.remove(c);
|
||||||
|
}
|
||||||
final CardCollection pref = CardLists.filterControlledBy(list, ai.getOpponent());
|
|
||||||
final CardCollection forced = CardLists.filterControlledBy(list, ai);
|
final CardCollection pref = CardLists.filterControlledBy(list, ComputerUtil.getOpponentFor(ai));
|
||||||
final Card source = sa.getHostCard();
|
final CardCollection forced = CardLists.filterControlledBy(list, ai);
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
|
||||||
if (pref.isEmpty()) {
|
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||||
break;
|
if (pref.isEmpty()) {
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
Card c;
|
|
||||||
if (CardLists.getNotType(pref, "Creature").size() == 0) {
|
Card c;
|
||||||
c = ComputerUtilCard.getBestCreatureAI(pref);
|
if (CardLists.getNotType(pref, "Creature").size() == 0) {
|
||||||
} else {
|
c = ComputerUtilCard.getBestCreatureAI(pref);
|
||||||
c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
|
} else {
|
||||||
}
|
c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
|
||||||
|
}
|
||||||
pref.remove(c);
|
|
||||||
|
pref.remove(c);
|
||||||
sa.getTargets().add(c);
|
|
||||||
}
|
sa.getTargets().add(c);
|
||||||
|
}
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
|
||||||
if (forced.isEmpty()) {
|
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
break;
|
if (forced.isEmpty()) {
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
// TODO - if forced targeting, just pick something without the given
|
|
||||||
// keyword
|
// TODO - if forced targeting, just pick something without the given
|
||||||
Card c;
|
// keyword
|
||||||
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
Card c;
|
||||||
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
||||||
} else {
|
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
||||||
c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
|
} else {
|
||||||
}
|
c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
|
||||||
|
}
|
||||||
forced.remove(c);
|
|
||||||
|
forced.remove(c);
|
||||||
sa.getTargets().add(c);
|
|
||||||
}
|
sa.getTargets().add(c);
|
||||||
|
}
|
||||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
|
||||||
sa.resetTargets();
|
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
return false;
|
sa.resetTargets();
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
return true;
|
|
||||||
} // pumpMandatoryTarget()
|
return true;
|
||||||
|
} // pumpMandatoryTarget()
|
||||||
@Override
|
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
@Override
|
||||||
final List<String> kws = sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList<String>();
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
final List<String> kws = sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList<String>();
|
||||||
if (sa.getTargetRestrictions() == null) {
|
|
||||||
if (mandatory) {
|
if (sa.getTargetRestrictions() == null) {
|
||||||
return true;
|
if (mandatory) {
|
||||||
}
|
return true;
|
||||||
} else {
|
}
|
||||||
return debuffTgtAI(ai, sa, kws, mandatory);
|
} else {
|
||||||
}
|
return debuffTgtAI(ai, sa, kws, mandatory);
|
||||||
|
}
|
||||||
return true;
|
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,54 +1,66 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.AiController;
|
import forge.ai.AiController;
|
||||||
import forge.ai.AiPlayDecision;
|
import forge.ai.AiPlayDecision;
|
||||||
import forge.ai.PlayerControllerAi;
|
import forge.ai.PlayerControllerAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.ai.SpellApiToAi;
|
import forge.ai.SpellApiToAi;
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
public class DelayedTriggerAi extends SpellAbilityAi {
|
public class DelayedTriggerAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
if ("Always".equals(sa.getParam("AILogic"))) {
|
if ("Always".equals(sa.getParam("AILogic"))) {
|
||||||
// TODO: improve ai
|
// TODO: improve ai
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
final String svarName = sa.getParam("Execute");
|
SpellAbility trigsa = null;
|
||||||
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
|
if (sa.hasAdditionalAbility("Execute")) {
|
||||||
trigsa.setActivatingPlayer(ai);
|
trigsa = sa.getAdditionalAbility("Execute");
|
||||||
|
} else {
|
||||||
if (trigsa instanceof AbilitySub) {
|
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
|
||||||
return SpellApiToAi.Converter.get(((AbilitySub) trigsa).getApi()).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
|
}
|
||||||
} else {
|
trigsa.setActivatingPlayer(ai);
|
||||||
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
|
||||||
}
|
if (trigsa instanceof AbilitySub) {
|
||||||
}
|
return SpellApiToAi.Converter.get(((AbilitySub) trigsa).getApi()).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
|
||||||
|
} else {
|
||||||
@Override
|
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
}
|
||||||
final String svarName = sa.getParam("Execute");
|
}
|
||||||
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
|
|
||||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
@Override
|
||||||
trigsa.setActivatingPlayer(ai);
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
SpellAbility trigsa = null;
|
||||||
if (!sa.hasParam("OptionalDecider")) {
|
if (sa.hasAdditionalAbility("Execute")) {
|
||||||
return aic.doTrigger(trigsa, true);
|
trigsa = sa.getAdditionalAbility("Execute");
|
||||||
} else {
|
} else {
|
||||||
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
|
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
|
||||||
}
|
}
|
||||||
}
|
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
|
trigsa.setActivatingPlayer(ai);
|
||||||
@Override
|
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
if (!sa.hasParam("OptionalDecider")) {
|
||||||
final String svarName = sa.getParam("Execute");
|
return aic.doTrigger(trigsa, true);
|
||||||
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getSVar(svarName), sa.getHostCard());
|
} else {
|
||||||
trigsa.setActivatingPlayer(ai);
|
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
|
||||||
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
@Override
|
||||||
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
SpellAbility trigsa = null;
|
||||||
|
if (sa.hasAdditionalAbility("Execute")) {
|
||||||
|
trigsa = sa.getAdditionalAbility("Execute");
|
||||||
|
} else {
|
||||||
|
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
|
||||||
|
}
|
||||||
|
trigsa.setActivatingPlayer(ai);
|
||||||
|
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,456 +1,462 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import forge.ai.AiController;
|
import forge.ai.*;
|
||||||
import forge.ai.AiProps;
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.game.card.*;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.game.cost.Cost;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.game.cost.CostPart;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.game.cost.CostSacrifice;
|
||||||
import forge.ai.PlayerControllerAi;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.ai.SpecialCardAi;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.player.Player;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.card.Card;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.game.card.CardFactoryUtil;
|
|
||||||
import forge.game.card.CardLists;
|
public class DestroyAi extends SpellAbilityAi {
|
||||||
import forge.game.card.CardPredicates;
|
@Override
|
||||||
import forge.game.card.CounterType;
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
import forge.game.cost.Cost;
|
return canPlayAI(ai, sa);
|
||||||
import forge.game.cost.CostPart;
|
}
|
||||||
import forge.game.cost.CostSacrifice;
|
|
||||||
import forge.game.phase.PhaseHandler;
|
@Override
|
||||||
import forge.game.phase.PhaseType;
|
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||||
import forge.game.player.Player;
|
// AI needs to be expanded, since this function can be pretty complex
|
||||||
import forge.game.spellability.SpellAbility;
|
// based on what the expected targets could be
|
||||||
import forge.game.spellability.TargetRestrictions;
|
final Cost abCost = sa.getPayCosts();
|
||||||
import forge.game.zone.ZoneType;
|
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
public class DestroyAi extends SpellAbilityAi {
|
final boolean noRegen = sa.hasParam("NoRegen");
|
||||||
@Override
|
final String logic = sa.getParam("AILogic");
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
boolean hasXCost = false;
|
||||||
return canPlayAI(ai, sa);
|
|
||||||
}
|
CardCollection list;
|
||||||
|
|
||||||
@Override
|
if (abCost != null) {
|
||||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||||
// AI needs to be expanded, since this function can be pretty complex
|
return false;
|
||||||
// based on what the expected targets could be
|
}
|
||||||
final Cost abCost = sa.getPayCosts();
|
|
||||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
final Card source = sa.getHostCard();
|
return false;
|
||||||
final boolean noRegen = sa.hasParam("NoRegen");
|
}
|
||||||
final String logic = sa.getParam("AILogic");
|
|
||||||
boolean hasXCost = false;
|
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||||
|
return false;
|
||||||
CardCollection list;
|
}
|
||||||
|
|
||||||
if (abCost != null) {
|
hasXCost = abCost.getCostMana() != null ? abCost.getCostMana().getAmountOfX() > 0 : false;
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
}
|
||||||
return false;
|
|
||||||
}
|
if ("AtOpponentsCombatOrAfter".equals(sa.getParam("AILogic"))) {
|
||||||
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
if (ph.getPlayerTurn() == ai || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if ("AtEOT".equals(sa.getParam("AILogic"))) {
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
return false;
|
if (!ph.is(PhaseType.END_OF_TURN)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
hasXCost = abCost.getCostMana() != null ? abCost.getCostMana().getAmountOfX() > 0 : false;
|
} else if ("AtEOTIfNotAttacking".equals(sa.getParam("AILogic"))) {
|
||||||
}
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
|
if (!ph.is(PhaseType.END_OF_TURN) || ai.getAttackedWithCreatureThisTurn()) {
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Targeting
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
if (abTgt != null) {
|
return false;
|
||||||
sa.resetTargets();
|
}
|
||||||
if (sa.hasParam("TargetingPlayer")) {
|
|
||||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
// Ability that's intended to destroy own useless token to trigger Grave Pacts
|
||||||
sa.setTargetingPlayer(targetingPlayer);
|
// should be fired at end of turn or when under attack after blocking to make opponent sac something
|
||||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
boolean havepact = false;
|
||||||
}
|
|
||||||
if ("MadSarkhanDragon".equals(logic)) {
|
// TODO replace it with look for a dies -> sacrifice trigger check
|
||||||
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
|
havepact |= ai.isCardInPlay("Grave Pact");
|
||||||
} else if ("Polymorph".equals(logic)) {
|
havepact |= ai.isCardInPlay("Butcher of Malakir");
|
||||||
list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
havepact |= ai.isCardInPlay("Dictate of Erebos");
|
||||||
if (list.isEmpty()) {
|
if ("Pactivator".equals(logic) && havepact) {
|
||||||
return false;
|
if ((!ai.getGame().getPhaseHandler().isPlayerTurn(ai))
|
||||||
}
|
&& ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)) || (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)))
|
||||||
for (Card c : list) {
|
&& (ai.getOpponents().getCreaturesInPlay().size() > 0)) {
|
||||||
if (c.hasKeyword("Indestructible")) {
|
ai.getController().chooseTargetsFor(sa);
|
||||||
sa.getTargets().add(c);
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Card worst = ComputerUtilCard.getWorstAI(list);
|
// Targeting
|
||||||
if (worst.isCreature() && ComputerUtilCard.evaluateCreature(worst) >= 200) {
|
if (abTgt != null) {
|
||||||
return false;
|
sa.resetTargets();
|
||||||
}
|
if (sa.hasParam("TargetingPlayer")) {
|
||||||
if (!worst.isCreature() && worst.getCMC() > 1) {
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
return false;
|
sa.setTargetingPlayer(targetingPlayer);
|
||||||
}
|
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||||
sa.getTargets().add(worst);
|
}
|
||||||
return true;
|
if ("MadSarkhanDragon".equals(logic)) {
|
||||||
}
|
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
|
||||||
list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
|
} else if (logic != null && logic.startsWith("MinLoyalty.")) {
|
||||||
if ("FatalPush".equals(logic)) {
|
int minLoyalty = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
|
||||||
final int cmcMax = ai.hasRevolt() ? 4 : 2;
|
if (source.getCounters(CounterType.LOYALTY) < minLoyalty) {
|
||||||
list = CardLists.filter(list, CardPredicates.lessCMC(cmcMax));
|
return false;
|
||||||
}
|
}
|
||||||
if (sa.hasParam("AITgts")) {
|
} else if ("Polymorph".equals(logic)) {
|
||||||
if (sa.getParam("AITgts").equals("BetterThanSource")) {
|
list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
if (source.isEnchanted()) {
|
if (list.isEmpty()) {
|
||||||
if (source.getEnchantedBy(false).get(0).getController().equals(ai)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
for (Card c : list) {
|
||||||
} else {
|
if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
|
||||||
final int value = ComputerUtilCard.evaluateCreature(source);
|
sa.getTargets().add(c);
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
return true;
|
||||||
@Override
|
}
|
||||||
public boolean apply(final Card c) {
|
}
|
||||||
return ComputerUtilCard.evaluateCreature(c) > value + 30;
|
Card worst = ComputerUtilCard.getWorstAI(list);
|
||||||
}
|
if (worst.isCreature() && ComputerUtilCard.evaluateCreature(worst) >= 200) {
|
||||||
});
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
if (!worst.isCreature() && worst.getCMC() > 1) {
|
||||||
list = CardLists.getValidCards(list, sa.getParam("AITgts"), sa.getActivatingPlayer(), source);
|
return false;
|
||||||
}
|
}
|
||||||
}
|
sa.getTargets().add(worst);
|
||||||
list = CardLists.getNotKeyword(list, "Indestructible");
|
return true;
|
||||||
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
}
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
|
||||||
@Override
|
if ("FatalPush".equals(logic)) {
|
||||||
public boolean apply(final Card c) {
|
final int cmcMax = ai.hasRevolt() ? 4 : 2;
|
||||||
//Check for cards that can be sacrificed in response
|
list = CardLists.filter(list, CardPredicates.lessCMC(cmcMax));
|
||||||
for (final SpellAbility ability : c.getAllSpellAbilities()) {
|
}
|
||||||
if (ability.isAbility()) {
|
|
||||||
final Cost cost = ability.getPayCosts();
|
// Filter AI-specific targets if provided
|
||||||
for (final CostPart part : cost.getCostParts()) {
|
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, true);
|
||||||
if (!(part instanceof CostSacrifice)) {
|
|
||||||
continue;
|
list = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
|
||||||
}
|
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||||
CostSacrifice sacCost = (CostSacrifice) part;
|
list = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, list, false);
|
||||||
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController())) {
|
}
|
||||||
return false;
|
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||||
}
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
}
|
@Override
|
||||||
}
|
public boolean apply(final Card c) {
|
||||||
}
|
//Check for cards that can be sacrificed in response
|
||||||
if (c.hasSVar("SacMe")) {
|
for (final SpellAbility ability : c.getAllSpellAbilities()) {
|
||||||
return false;
|
if (ability.isAbility()) {
|
||||||
}
|
final Cost cost = ability.getPayCosts();
|
||||||
//Check for undying
|
for (final CostPart part : cost.getCostParts()) {
|
||||||
return (!c.hasKeyword("Undying") || c.getCounters(CounterType.P1P1) > 0);
|
if (!(part instanceof CostSacrifice)) {
|
||||||
}
|
continue;
|
||||||
});
|
}
|
||||||
}
|
CostSacrifice sacCost = (CostSacrifice) part;
|
||||||
|
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController())) {
|
||||||
// If NoRegen is not set, filter out creatures that have a
|
return false;
|
||||||
// regeneration shield
|
}
|
||||||
if (!noRegen) {
|
}
|
||||||
// TODO filter out things that might be tougher?
|
}
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
}
|
||||||
@Override
|
if (c.hasSVar("SacMe")) {
|
||||||
public boolean apply(final Card c) {
|
return false;
|
||||||
return (c.getShieldCount() == 0 && !ComputerUtil.canRegenerate(ai, c));
|
}
|
||||||
}
|
//Check for undying
|
||||||
});
|
return (!c.hasKeyword(Keyword.UNDYING) || c.getCounters(CounterType.P1P1) > 0);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
if (list.isEmpty()) {
|
}
|
||||||
return false;
|
|
||||||
}
|
// If NoRegen is not set, filter out creatures that have a
|
||||||
|
// regeneration shield
|
||||||
int maxTargets = abTgt.getMaxTargets(sa.getHostCard(), sa);
|
if (!noRegen) {
|
||||||
|
// TODO filter out things that might be tougher?
|
||||||
if (hasXCost) {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
|
@Override
|
||||||
maxTargets = Math.min(ComputerUtilMana.determineMaxAffordableX(ai, sa), abTgt.getMaxTargets(sa.getHostCard(), sa));
|
public boolean apply(final Card c) {
|
||||||
}
|
return (c.getShieldCount() == 0 && !ComputerUtil.canRegenerate(ai, c));
|
||||||
if (sa.hasParam("AIMaxTgtsCount")) {
|
}
|
||||||
// Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified
|
});
|
||||||
// TODO: is there a better way to predict things like "sac X" costs without needing a special AI variable?
|
}
|
||||||
maxTargets = Math.min(CardFactoryUtil.xCount(sa.getHostCard(), "Count$" + sa.getParam("AIMaxTgtsCount")), maxTargets);
|
|
||||||
}
|
if (list.isEmpty()) {
|
||||||
|
return false;
|
||||||
if (maxTargets == 0) {
|
}
|
||||||
// can't afford X or otherwise target anything
|
|
||||||
return false;
|
int maxTargets = abTgt.getMaxTargets(sa.getHostCard(), sa);
|
||||||
}
|
|
||||||
|
if (hasXCost) {
|
||||||
// target loop
|
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
|
||||||
while (sa.getTargets().getNumTargeted() < maxTargets) {
|
maxTargets = Math.min(ComputerUtilMana.determineMaxAffordableX(ai, sa), abTgt.getMaxTargets(sa.getHostCard(), sa));
|
||||||
if (list.isEmpty()) {
|
// X can't be more than the lands we have in our hand for "discard X lands"!
|
||||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
if ("ScorchedEarth".equals(logic)) {
|
||||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
int lands = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size();
|
||||||
sa.resetTargets();
|
maxTargets = Math.min(maxTargets, lands);
|
||||||
return false;
|
}
|
||||||
} else {
|
}
|
||||||
// TODO is this good enough? for up to amounts?
|
if (sa.hasParam("AIMaxTgtsCount")) {
|
||||||
break;
|
// Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified
|
||||||
}
|
// TODO: is there a better way to predict things like "sac X" costs without needing a special AI variable?
|
||||||
}
|
maxTargets = Math.min(CardFactoryUtil.xCount(sa.getHostCard(), "Count$" + sa.getParam("AIMaxTgtsCount")), maxTargets);
|
||||||
|
}
|
||||||
Card choice = null;
|
|
||||||
// If the targets are only of one type, take the best
|
if (maxTargets == 0) {
|
||||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
// can't afford X or otherwise target anything
|
||||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
return false;
|
||||||
if ("OppDestroyYours".equals(logic)) {
|
}
|
||||||
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
|
|
||||||
if (ComputerUtilCard.evaluateCreature(aiBest) > ComputerUtilCard.evaluateCreature(choice) - 40) {
|
// target loop
|
||||||
return false;
|
while (sa.getTargets().getNumTargeted() < maxTargets) {
|
||||||
}
|
if (list.isEmpty()) {
|
||||||
}
|
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||||
if ("Pongify".equals(logic)) {
|
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||||
final Card token = TokenAi.spawnToken(choice.getController(), sa.getSubAbility());
|
sa.resetTargets();
|
||||||
if (token == null) {
|
return false;
|
||||||
return true; // becomes Terminate
|
} else {
|
||||||
} else {
|
// TODO is this good enough? for up to amounts?
|
||||||
if (source.getGame().getPhaseHandler().getPhase()
|
break;
|
||||||
.isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) || // prevent surprise combatant
|
}
|
||||||
ComputerUtilCard.evaluateCreature(choice) < 1.5
|
}
|
||||||
* ComputerUtilCard.evaluateCreature(token)) {
|
|
||||||
return false;
|
Card choice = null;
|
||||||
}
|
// If the targets are only of one type, take the best
|
||||||
}
|
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||||
}
|
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
} else if (CardLists.getNotType(list, "Land").isEmpty()) {
|
if ("OppDestroyYours".equals(logic)) {
|
||||||
choice = ComputerUtilCard.getBestLandAI(list);
|
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
|
||||||
|
if (ComputerUtilCard.evaluateCreature(aiBest) > ComputerUtilCard.evaluateCreature(choice) - 40) {
|
||||||
if ("LandForLand".equals(logic) || "GhostQuarter".equals(logic)) {
|
return false;
|
||||||
// Strip Mine, Wasteland - cut short if the relevant logic fails
|
}
|
||||||
if (!doLandForLandRemovalLogic(sa, ai, choice, logic)) {
|
}
|
||||||
return false;
|
if ("Pongify".equals(logic)) {
|
||||||
}
|
final Card token = TokenAi.spawnToken(choice.getController(), sa.getSubAbility());
|
||||||
}
|
if (token == null) {
|
||||||
} else {
|
return true; // becomes Terminate
|
||||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
} else {
|
||||||
}
|
if (source.getGame().getPhaseHandler().getPhase()
|
||||||
//option to hold removal instead only applies for single targeted removal
|
.isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) || // prevent surprise combatant
|
||||||
if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
|
ComputerUtilCard.evaluateCreature(choice) < 1.5
|
||||||
if (!ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
* ComputerUtilCard.evaluateCreature(token)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (choice == null) { // can't find anything left
|
} else if (CardLists.getNotType(list, "Land").isEmpty()) {
|
||||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
choice = ComputerUtilCard.getBestLandAI(list);
|
||||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
|
||||||
sa.resetTargets();
|
if ("LandForLand".equals(logic) || "GhostQuarter".equals(logic)) {
|
||||||
return false;
|
// Strip Mine, Wasteland - cut short if the relevant logic fails
|
||||||
} else {
|
if (!doLandForLandRemovalLogic(sa, ai, choice, logic)) {
|
||||||
// TODO is this good enough? for up to amounts?
|
return false;
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Don't destroy stolen permanents when the stealing aura can be destroyed
|
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||||
if (choice.getOwner() == ai) {
|
}
|
||||||
for (Card aura : choice.getEnchantedBy(false)) {
|
//option to hold removal instead only applies for single targeted removal
|
||||||
SpellAbility sp = aura.getFirstSpellAbility();
|
if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
|
||||||
if (sp != null && "GainControl".equals(sp.getParam("AILogic"))
|
if (!ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
||||||
&& aura.getController() != ai && sa.canTarget(aura)) {
|
return false;
|
||||||
choice = aura;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
if (choice == null) { // can't find anything left
|
||||||
}
|
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||||
list.remove(choice);
|
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||||
sa.getTargets().add(choice);
|
sa.resetTargets();
|
||||||
}
|
return false;
|
||||||
} else if (sa.hasParam("Defined")) {
|
} else {
|
||||||
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
// TODO is this good enough? for up to amounts?
|
||||||
if ("WillSkipTurn".equals(logic) && (sa.getHostCard().getController().equals(ai)
|
break;
|
||||||
|| ai.getCreaturesInPlay().size() < ai.getOpponent().getCreaturesInPlay().size()
|
}
|
||||||
|| !source.getGame().getPhaseHandler().isPlayerTurn(ai)
|
} else {
|
||||||
|| ai.getLife() <= 5)) {
|
// Don't destroy stolen permanents when the stealing aura can be destroyed
|
||||||
// Basic ai logic for Lethal Vapors
|
if (choice.getOwner() == ai) {
|
||||||
return false;
|
for (Card aura : choice.getEnchantedBy(false)) {
|
||||||
}
|
SpellAbility sp = aura.getFirstSpellAbility();
|
||||||
|
if (sp != null && "GainControl".equals(sp.getParam("AILogic"))
|
||||||
if (list.isEmpty()
|
&& aura.getController() != ai && sa.canTarget(aura)) {
|
||||||
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|
choice = aura;
|
||||||
|| CardLists.getNotKeyword(list, "Indestructible").isEmpty()) {
|
}
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
list.remove(choice);
|
||||||
}
|
sa.getTargets().add(choice);
|
||||||
|
}
|
||||||
@Override
|
} else if (sa.hasParam("Defined")) {
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
if ("WillSkipTurn".equals(logic) && (sa.getHostCard().getController().equals(ai)
|
||||||
final Card source = sa.getHostCard();
|
|| ai.getCreaturesInPlay().size() < ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size()
|
||||||
final boolean noRegen = sa.hasParam("NoRegen");
|
|| !source.getGame().getPhaseHandler().isPlayerTurn(ai)
|
||||||
if (tgt != null) {
|
|| ai.getLife() <= 5)) {
|
||||||
sa.resetTargets();
|
// Basic ai logic for Lethal Vapors
|
||||||
|
return false;
|
||||||
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
}
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
|
||||||
|
if (list.isEmpty()
|
||||||
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|
||||||
return false;
|
|| CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE).isEmpty()) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
CardCollection preferred = CardLists.getNotKeyword(list, "Indestructible");
|
}
|
||||||
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
|
return true;
|
||||||
|
}
|
||||||
// If NoRegen is not set, filter out creatures that have a
|
|
||||||
// regeneration shield
|
@Override
|
||||||
if (!noRegen) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
// TODO filter out things that could regenerate in response?
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
// might be tougher?
|
final Card source = sa.getHostCard();
|
||||||
preferred = CardLists.filter(preferred, new Predicate<Card>() {
|
final boolean noRegen = sa.hasParam("NoRegen");
|
||||||
@Override
|
if (tgt != null) {
|
||||||
public boolean apply(final Card c) {
|
sa.resetTargets();
|
||||||
return c.getShieldCount() == 0;
|
|
||||||
}
|
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
||||||
});
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||||
}
|
|
||||||
|
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
if (sa.hasParam("AITgts")) {
|
return false;
|
||||||
if (sa.getParam("AITgts").equals("BetterThanSource")) {
|
}
|
||||||
if (source.isEnchanted()) {
|
|
||||||
if (source.getEnchantedBy(false).get(0).getController().equals(ai)) {
|
CardCollection preferred = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
|
||||||
preferred.clear();
|
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
|
||||||
}
|
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
|
||||||
} else {
|
preferred = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, preferred, false);
|
||||||
final int value = ComputerUtilCard.evaluateCreature(source);
|
}
|
||||||
preferred = CardLists.filter(preferred, new Predicate<Card>() {
|
|
||||||
@Override
|
// If NoRegen is not set, filter out creatures that have a
|
||||||
public boolean apply(final Card c) {
|
// regeneration shield
|
||||||
return ComputerUtilCard.evaluateCreature(c) > value + 30;
|
if (!noRegen) {
|
||||||
}
|
// TODO filter out things that could regenerate in response?
|
||||||
});
|
// might be tougher?
|
||||||
}
|
preferred = CardLists.filter(preferred, new Predicate<Card>() {
|
||||||
} else {
|
@Override
|
||||||
preferred = CardLists.getValidCards(preferred, sa.getParam("AITgts"), sa.getActivatingPlayer(), source);
|
public boolean apply(final Card c) {
|
||||||
}
|
return c.getShieldCount() == 0;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
for (final Card c : preferred) {
|
}
|
||||||
list.remove(c);
|
|
||||||
}
|
// Filter AI-specific targets if provided
|
||||||
|
preferred = ComputerUtil.filterAITgts(sa, ai, (CardCollection)preferred, true);
|
||||||
if (preferred.isEmpty() && !mandatory) {
|
|
||||||
return false;
|
for (final Card c : preferred) {
|
||||||
}
|
list.remove(c);
|
||||||
|
}
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
|
||||||
if (preferred.isEmpty()) {
|
if (preferred.isEmpty() && !mandatory) {
|
||||||
if (sa.getTargets().getNumTargeted() == 0
|
return false;
|
||||||
|| sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
}
|
||||||
if (!mandatory) {
|
|
||||||
sa.resetTargets();
|
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
return false;
|
if (preferred.isEmpty()) {
|
||||||
} else {
|
if (sa.getTargets().getNumTargeted() == 0
|
||||||
break;
|
|| sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
}
|
if (!mandatory) {
|
||||||
} else {
|
sa.resetTargets();
|
||||||
break;
|
return false;
|
||||||
}
|
} else {
|
||||||
} else {
|
break;
|
||||||
Card c;
|
}
|
||||||
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
|
} else {
|
||||||
c = ComputerUtilCard.getBestCreatureAI(preferred);
|
break;
|
||||||
} else if (CardLists.getNotType(preferred, "Land").isEmpty()) {
|
}
|
||||||
c = ComputerUtilCard.getBestLandAI(preferred);
|
} else {
|
||||||
} else {
|
Card c;
|
||||||
c = ComputerUtilCard.getMostExpensivePermanentAI(preferred, sa, false);
|
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
|
||||||
}
|
c = ComputerUtilCard.getBestCreatureAI(preferred);
|
||||||
sa.getTargets().add(c);
|
} else if (CardLists.getNotType(preferred, "Land").isEmpty()) {
|
||||||
preferred.remove(c);
|
c = ComputerUtilCard.getBestLandAI(preferred);
|
||||||
}
|
} else {
|
||||||
}
|
c = ComputerUtilCard.getMostExpensivePermanentAI(preferred, sa, false);
|
||||||
|
}
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
sa.getTargets().add(c);
|
||||||
if (list.isEmpty()) {
|
preferred.remove(c);
|
||||||
break;
|
}
|
||||||
} else {
|
}
|
||||||
Card c;
|
|
||||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
if (!sa.getUniqueTargets().isEmpty() && sa.getParent().getApi() == ApiType.Destroy
|
if (list.isEmpty()) {
|
||||||
&& sa.getUniqueTargets().get(0) instanceof Card) {
|
break;
|
||||||
// basic ai for Diaochan
|
} else {
|
||||||
c = (Card) sa.getUniqueTargets().get(0);
|
Card c;
|
||||||
} else {
|
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||||
c = ComputerUtilCard.getWorstCreatureAI(list);
|
if (!sa.getUniqueTargets().isEmpty() && sa.getParent().getApi() == ApiType.Destroy
|
||||||
}
|
&& sa.getUniqueTargets().get(0) instanceof Card) {
|
||||||
} else {
|
// basic ai for Diaochan
|
||||||
c = ComputerUtilCard.getCheapestPermanentAI(list, sa, false);
|
c = (Card) sa.getUniqueTargets().get(0);
|
||||||
}
|
} else {
|
||||||
sa.getTargets().add(c);
|
c = ComputerUtilCard.getWorstCreatureAI(list);
|
||||||
list.remove(c);
|
}
|
||||||
}
|
} else {
|
||||||
}
|
c = ComputerUtilCard.getCheapestPermanentAI(list, sa, false);
|
||||||
|
}
|
||||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
sa.getTargets().add(c);
|
||||||
return false;
|
list.remove(c);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
if (!mandatory) {
|
|
||||||
return false;
|
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
return true;
|
if (!mandatory) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
public boolean doLandForLandRemovalLogic(SpellAbility sa, Player ai, Card tgtLand, String logic) {
|
}
|
||||||
if (tgtLand == null) { return false; }
|
|
||||||
|
return true;
|
||||||
Player tgtPlayer = tgtLand.getController();
|
}
|
||||||
int oppLandsOTB = tgtPlayer.getLandsInPlay().size();
|
|
||||||
|
public boolean doLandForLandRemovalLogic(SpellAbility sa, Player ai, Card tgtLand, String logic) {
|
||||||
// AI profile-dependent properties
|
if (tgtLand == null) { return false; }
|
||||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
|
||||||
int amountNoTempoCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK);
|
Player tgtPlayer = tgtLand.getController();
|
||||||
int amountNoTimingCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK);
|
int oppLandsOTB = tgtPlayer.getLandsInPlay().size();
|
||||||
int amountLandsInHand = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE);
|
|
||||||
int amountLandsToManalock = aic.getIntProperty(AiProps.STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING);
|
// AI profile-dependent properties
|
||||||
boolean highPriorityIfNoLandDrop = aic.getBooleanProperty(AiProps.STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP);
|
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
|
int amountNoTempoCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK);
|
||||||
// if the opponent didn't play a land and has few lands OTB, might be worth mana-locking him
|
int amountNoTimingCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK);
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
int amountLandsInHand = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE);
|
||||||
boolean oppSkippedLandDrop = (tgtPlayer.getLandsPlayedLastTurn() == 0 && ph.isPlayerTurn(ai))
|
int amountLandsToManalock = aic.getIntProperty(AiProps.STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING);
|
||||||
|| (tgtPlayer.getLandsPlayedThisTurn() == 0 && ph.isPlayerTurn(tgtPlayer) && ph.getPhase().isAfter(PhaseType.MAIN2));
|
boolean highPriorityIfNoLandDrop = aic.getBooleanProperty(AiProps.STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP);
|
||||||
boolean canManaLock = oppLandsOTB <= amountLandsToManalock && oppSkippedLandDrop;
|
|
||||||
|
// if the opponent didn't play a land and has few lands OTB, might be worth mana-locking him
|
||||||
// Best target is a basic land, and there's only one of it, so destroying it may potentially color-lock the opponent
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
// (triggers either if the opponent skipped a land drop or if there are quite a few lands already in play but only one of the given type)
|
boolean oppSkippedLandDrop = (tgtPlayer.getLandsPlayedLastTurn() == 0 && ph.isPlayerTurn(ai))
|
||||||
CardCollection oppLands = tgtPlayer.getLandsInPlay();
|
|| (tgtPlayer.getLandsPlayedThisTurn() == 0 && ph.isPlayerTurn(tgtPlayer) && ph.getPhase().isAfter(PhaseType.MAIN2));
|
||||||
boolean canColorLock = (oppSkippedLandDrop || oppLands.size() > 3)
|
boolean canManaLock = oppLandsOTB <= amountLandsToManalock && oppSkippedLandDrop;
|
||||||
&& tgtLand.isBasicLand() && CardLists.filter(oppLands, CardPredicates.nameEquals(tgtLand.getName())).size() == 1;
|
|
||||||
|
// Best target is a basic land, and there's only one of it, so destroying it may potentially color-lock the opponent
|
||||||
// Non-basic lands are currently not ranked in any way in ComputerUtilCard#getBestLandAI, so if a non-basic land is best target,
|
// (triggers either if the opponent skipped a land drop or if there are quite a few lands already in play but only one of the given type)
|
||||||
// consider killing it off unless there's too much potential tempo loss.
|
CardCollection oppLands = tgtPlayer.getLandsInPlay();
|
||||||
// TODO: actually rank non-basics in that method and then kill off the potentially dangerous (manlands, Valakut) or lucrative
|
boolean canColorLock = (oppSkippedLandDrop || oppLands.size() > 3)
|
||||||
// (dual/triple mana that opens access to a certain color) lands
|
&& tgtLand.isBasicLand() && CardLists.filter(oppLands, CardPredicates.nameEquals(tgtLand.getName())).size() == 1;
|
||||||
boolean nonBasicTgt = !tgtLand.isBasicLand();
|
|
||||||
|
// Non-basic lands are currently not ranked in any way in ComputerUtilCard#getBestLandAI, so if a non-basic land is best target,
|
||||||
// Try not to lose tempo too much and not to mana-screw yourself when considering this logic
|
// consider killing it off unless there's too much potential tempo loss.
|
||||||
int numLandsInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
|
// TODO: actually rank non-basics in that method and then kill off the potentially dangerous (manlands, Valakut) or lucrative
|
||||||
int numLandsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
|
// (dual/triple mana that opens access to a certain color) lands
|
||||||
|
boolean nonBasicTgt = !tgtLand.isBasicLand();
|
||||||
// If the opponent skipped a land drop, consider not looking at having the extra land in hand if the profile allows it
|
|
||||||
boolean isHighPriority = highPriorityIfNoLandDrop && oppSkippedLandDrop;
|
// Try not to lose tempo too much and not to mana-screw yourself when considering this logic
|
||||||
|
int numLandsInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
|
||||||
boolean timingCheck = canManaLock || canColorLock || nonBasicTgt;
|
int numLandsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
|
||||||
boolean tempoCheck = numLandsOTB >= amountNoTempoCheck
|
|
||||||
|| ((numLandsInHand >= amountLandsInHand || isHighPriority) && ((numLandsInHand + numLandsOTB >= amountNoTimingCheck) || timingCheck));
|
// If the opponent skipped a land drop, consider not looking at having the extra land in hand if the profile allows it
|
||||||
|
boolean isHighPriority = highPriorityIfNoLandDrop && oppSkippedLandDrop;
|
||||||
// For Ghost Quarter, only use it if you have either more lands in play than your opponent
|
|
||||||
// or the same number of lands but an extra land in hand (otherwise the AI plays too suboptimally)
|
boolean timingCheck = canManaLock || canColorLock || nonBasicTgt;
|
||||||
if ("GhostQuarter".equals(logic)) {
|
boolean tempoCheck = numLandsOTB >= amountNoTempoCheck
|
||||||
return tempoCheck && (numLandsOTB > oppLands.size() || (numLandsOTB == oppLands.size() && numLandsInHand > 0));
|
|| ((numLandsInHand >= amountLandsInHand || isHighPriority) && ((numLandsInHand + numLandsOTB >= amountNoTimingCheck) || timingCheck));
|
||||||
} else {
|
|
||||||
return tempoCheck;
|
// For Ghost Quarter, only use it if you have either more lands in play than your opponent
|
||||||
}
|
// or the same number of lands but an extra land in hand (otherwise the AI plays too suboptimally)
|
||||||
}
|
if ("GhostQuarter".equals(logic)) {
|
||||||
}
|
return tempoCheck && (numLandsOTB > oppLands.size() || (numLandsOTB == oppLands.size() && numLandsInHand > 0));
|
||||||
|
} else {
|
||||||
|
return tempoCheck;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,158 +1,177 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
|
||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.player.Player;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.player.Player;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.game.combat.Combat;
|
||||||
public class DestroyAllAi extends SpellAbilityAi {
|
|
||||||
|
public class DestroyAllAi extends SpellAbilityAi {
|
||||||
private static final Predicate<Card> predicate = new Predicate<Card>() {
|
|
||||||
@Override
|
private static final Predicate<Card> predicate = new Predicate<Card>() {
|
||||||
public boolean apply(final Card c) {
|
@Override
|
||||||
return !(c.hasKeyword("Indestructible") || c.getSVar("SacMe").length() > 0);
|
public boolean apply(final Card c) {
|
||||||
}
|
return !(c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.getSVar("SacMe").length() > 0);
|
||||||
};
|
}
|
||||||
|
};
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
/* (non-Javadoc)
|
||||||
*/
|
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||||
@Override
|
*/
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
@Override
|
||||||
if (mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
return true;
|
if (mandatory) {
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
return doMassRemovalLogic(ai, sa);
|
|
||||||
}
|
return doMassRemovalLogic(ai, sa);
|
||||||
|
}
|
||||||
@Override
|
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
@Override
|
||||||
//TODO: Check for bad outcome
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
return true;
|
//TODO: Check for bad outcome
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
@Override
|
|
||||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
@Override
|
||||||
// AI needs to be expanded, since this function can be pretty complex
|
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||||
// based on what the expected targets could be
|
// AI needs to be expanded, since this function can be pretty complex
|
||||||
final Cost abCost = sa.getPayCosts();
|
// based on what the expected targets could be
|
||||||
final Card source = sa.getHostCard();
|
final Cost abCost = sa.getPayCosts();
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
if (abCost != null) {
|
|
||||||
// AI currently disabled for some costs
|
if (abCost != null) {
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
// AI currently disabled for some costs
|
||||||
return false;
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// prevent run-away activations - first time will always return true
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
// prevent run-away activations - first time will always return true
|
||||||
return false;
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
return doMassRemovalLogic(ai, sa);
|
|
||||||
}
|
return doMassRemovalLogic(ai, sa);
|
||||||
|
}
|
||||||
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
|
|
||||||
final Card source = sa.getHostCard();
|
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
|
||||||
Player opponent = ai.getOpponent(); // TODO: how should this AI logic work for multiplayer and getOpponents()?
|
final Card source = sa.getHostCard();
|
||||||
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
final int CREATURE_EVAL_THRESHOLD = 200;
|
Player opponent = ComputerUtil.getOpponentFor(ai); // TODO: how should this AI logic work for multiplayer and getOpponents()?
|
||||||
|
|
||||||
String valid = "";
|
final int CREATURE_EVAL_THRESHOLD = 200;
|
||||||
if (sa.hasParam("ValidCards")) {
|
|
||||||
valid = sa.getParam("ValidCards");
|
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
|
||||||
|
}
|
||||||
if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
|
||||||
// Set PayX here to maximum value.
|
String valid = "";
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
if (sa.hasParam("ValidCards")) {
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
valid = sa.getParam("ValidCards");
|
||||||
valid = valid.replace("X", Integer.toString(xPay));
|
}
|
||||||
}
|
|
||||||
|
if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||||
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield),
|
// Set PayX here to maximum value.
|
||||||
valid.split(","), source.getController(), source, sa);
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
source.setSVar("PayX", Integer.toString(xPay));
|
||||||
source.getController(), source, sa);
|
valid = valid.replace("X", Integer.toString(xPay));
|
||||||
|
}
|
||||||
opplist = CardLists.filter(opplist, predicate);
|
|
||||||
ailist = CardLists.filter(ailist, predicate);
|
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield),
|
||||||
if (opplist.isEmpty()) {
|
valid.split(","), source.getController(), source, sa);
|
||||||
return false;
|
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
||||||
}
|
source.getController(), source, sa);
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
opplist = CardLists.filter(opplist, predicate);
|
||||||
sa.resetTargets();
|
ailist = CardLists.filter(ailist, predicate);
|
||||||
if (sa.canTarget(opponent)) {
|
if (opplist.isEmpty()) {
|
||||||
sa.getTargets().add(opponent);
|
return false;
|
||||||
ailist.clear();
|
}
|
||||||
} else {
|
|
||||||
return false;
|
if (sa.usesTargeting()) {
|
||||||
}
|
sa.resetTargets();
|
||||||
}
|
if (sa.canTarget(opponent)) {
|
||||||
|
sa.getTargets().add(opponent);
|
||||||
// if only creatures are affected evaluate both lists and pass only if
|
ailist.clear();
|
||||||
// human creatures are more valuable
|
} else {
|
||||||
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
return false;
|
||||||
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
}
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
// If effect is destroying creatures and AI is about to lose, activate effect anyway no matter what!
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
|
||||||
return false;
|
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) {
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
// test whether the human can kill the ai next turn
|
|
||||||
Combat combat = new Combat(opponent);
|
// 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
|
||||||
boolean containsAttacker = false;
|
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
|
||||||
for (Card att : opponent.getCreaturesInPlay()) {
|
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInDanger(ai, ai.getGame().getCombat()))
|
||||||
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
&& ((ComputerUtilCard.evaluatePermanentList(ailist) - 6) >= ComputerUtilCard.evaluatePermanentList(opplist))) {
|
||||||
combat.addAttacker(att, ai);
|
return true;
|
||||||
containsAttacker = containsAttacker | opplist.contains(att);
|
}
|
||||||
}
|
|
||||||
}
|
// if only creatures are affected evaluate both lists and pass only if
|
||||||
if (!containsAttacker) {
|
// human creatures are more valuable
|
||||||
return false;
|
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
||||||
}
|
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
||||||
AiBlockController block = new AiBlockController(ai);
|
return true;
|
||||||
block.assignBlockersForCombat(combat);
|
}
|
||||||
|
|
||||||
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
} // only lands involved
|
// test whether the human can kill the ai next turn
|
||||||
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
|
Combat combat = new Combat(opponent);
|
||||||
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds") && !opplist.isEmpty()) {
|
boolean containsAttacker = false;
|
||||||
return true;
|
for (Card att : opponent.getCreaturesInPlay()) {
|
||||||
}
|
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
||||||
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
|
combat.addAttacker(att, ai);
|
||||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
containsAttacker = containsAttacker | opplist.contains(att);
|
||||||
CardCollection oppCreatures = opponent.getCreaturesInPlay();
|
}
|
||||||
if (!oppCreatures.isEmpty()) {
|
}
|
||||||
if (ComputerUtilCard.evaluateCreatureList(aiCreatures) < ComputerUtilCard.evaluateCreatureList(oppCreatures) + CREATURE_EVAL_THRESHOLD) {
|
if (!containsAttacker) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
AiBlockController block = new AiBlockController(ai);
|
||||||
// check if the AI would lose more lands than the opponent would
|
block.assignBlockersForCombat(combat);
|
||||||
if (ComputerUtilCard.evaluatePermanentList(ailist) > ComputerUtilCard.evaluatePermanentList(opplist) + 1) {
|
|
||||||
return false;
|
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
||||||
}
|
return true;
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
|
}
|
||||||
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
|
return false;
|
||||||
return false;
|
} // only lands involved
|
||||||
}
|
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
|
||||||
|
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds") && !opplist.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
|
||||||
|
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||||
|
CardCollection oppCreatures = opponent.getCreaturesInPlay();
|
||||||
|
if (!oppCreatures.isEmpty()) {
|
||||||
|
if (ComputerUtilCard.evaluateCreatureList(aiCreatures) < ComputerUtilCard.evaluateCreatureList(oppCreatures) + CREATURE_EVAL_THRESHOLD) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check if the AI would lose more lands than the opponent would
|
||||||
|
if (ComputerUtilCard.evaluatePermanentList(ailist) > ComputerUtilCard.evaluatePermanentList(opplist) + 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
|
||||||
|
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,159 +1,155 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.game.Game;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.game.card.Card;
|
||||||
import forge.ai.SpecialCardAi;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.game.player.Player;
|
||||||
import forge.game.Game;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.card.Card;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.util.TextUtil;
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
|
||||||
import forge.game.spellability.AbilitySub;
|
public class DigAi extends SpellAbilityAi {
|
||||||
import forge.game.spellability.SpellAbility;
|
/* (non-Javadoc)
|
||||||
import forge.game.zone.ZoneType;
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
import forge.util.TextUtil;
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
public class DigAi extends SpellAbilityAi {
|
final Game game = ai.getGame();
|
||||||
/* (non-Javadoc)
|
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
final Card host = sa.getHostCard();
|
||||||
*/
|
Player libraryOwner = ai;
|
||||||
@Override
|
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
if (sa.usesTargeting()) {
|
||||||
final Game game = ai.getGame();
|
sa.resetTargets();
|
||||||
Player opp = ai.getOpponent();
|
if (!opp.canBeTargetedBy(sa)) {
|
||||||
final Card host = sa.getHostCard();
|
return false;
|
||||||
Player libraryOwner = ai;
|
} else {
|
||||||
|
sa.getTargets().add(opp);
|
||||||
if (sa.usesTargeting()) {
|
}
|
||||||
sa.resetTargets();
|
libraryOwner = opp;
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
}
|
||||||
return false;
|
|
||||||
} else {
|
// return false if nothing to dig into
|
||||||
sa.getTargets().add(opp);
|
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||||
}
|
return false;
|
||||||
libraryOwner = opp;
|
}
|
||||||
}
|
|
||||||
|
if ("Never".equals(sa.getParam("AILogic"))) {
|
||||||
// return false if nothing to dig into
|
return false;
|
||||||
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
|
} else if ("AtOppEndOfTurn".equals(sa.getParam("AILogic"))) {
|
||||||
return false;
|
return game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("Never".equals(sa.getParam("AILogic"))) {
|
// don't deck yourself
|
||||||
return false;
|
if (sa.hasParam("DestinationZone2") && !"Library".equals(sa.getParam("DestinationZone2"))) {
|
||||||
}
|
int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
|
||||||
|
if (libraryOwner == ai && ai.getCardsIn(ZoneType.Library).size() <= numToDig + 2) {
|
||||||
// don't deck yourself
|
return false;
|
||||||
if (sa.hasParam("DestinationZone2") && !"Library".equals(sa.getParam("DestinationZone2"))) {
|
}
|
||||||
int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
|
}
|
||||||
if (libraryOwner == ai && ai.getCardsIn(ZoneType.Library).size() <= numToDig + 2) {
|
|
||||||
return false;
|
// Don't use draw abilities before main 2 if possible
|
||||||
}
|
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||||
}
|
&& !sa.hasParam("DestinationZone") && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||||
|
return false;
|
||||||
// Don't use draw abilities before main 2 if possible
|
}
|
||||||
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
|
||||||
&& !sa.hasParam("DestinationZone") && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
final String num = sa.getParam("DigNum");
|
||||||
return false;
|
if (num != null && num.equals("X") && host.getSVar(num).equals("Count$xPaid")) {
|
||||||
}
|
// By default, set PayX here to maximum value.
|
||||||
|
if (!(sa instanceof AbilitySub) || host.getSVar("PayX").equals("")) {
|
||||||
final String num = sa.getParam("DigNum");
|
int manaToSave = 0;
|
||||||
if (num != null && num.equals("X") && host.getSVar(num).equals("Count$xPaid")) {
|
|
||||||
// By default, set PayX here to maximum value.
|
// Special logic that asks the AI to conserve a certain amount of mana when paying X
|
||||||
if (!(sa instanceof AbilitySub) || host.getSVar("PayX").equals("")) {
|
if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) {
|
||||||
int manaToSave = 0;
|
manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
|
||||||
|
}
|
||||||
// Special logic that asks the AI to conserve a certain amount of mana when paying X
|
|
||||||
if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) {
|
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave;
|
||||||
manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
|
if (numCards <= 0) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave;
|
host.setSVar("PayX", Integer.toString(numCards));
|
||||||
if (numCards <= 0) {
|
}
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
host.setSVar("PayX", Integer.toString(numCards));
|
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|
||||||
return true;
|
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||||
}
|
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)
|
||||||
|
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
|
||||||
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|
&& !ComputerUtil.activateForCost(sa, ai)) {
|
||||||
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
|
return false;
|
||||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)
|
}
|
||||||
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
|
|
||||||
&& !ComputerUtil.activateForCost(sa, ai)) {
|
if ("MadSarkhanDigDmg".equals(sa.getParam("AILogic"))) {
|
||||||
return false;
|
return SpecialCardAi.SarkhanTheMad.considerDig(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("MadSarkhanDigDmg".equals(sa.getParam("AILogic"))) {
|
return !ComputerUtil.preventRunAwayActivations(sa);
|
||||||
return SpecialCardAi.SarkhanTheMad.considerDig(ai, sa);
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
return !ComputerUtil.preventRunAwayActivations(sa);
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
}
|
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
@Override
|
sa.resetTargets();
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
final Player opp = ai.getOpponent();
|
sa.getTargets().add(opp);
|
||||||
if (sa.usesTargeting()) {
|
} else if (mandatory && sa.canTarget(ai)) {
|
||||||
sa.resetTargets();
|
sa.getTargets().add(ai);
|
||||||
if (mandatory && sa.canTarget(opp)) {
|
}
|
||||||
sa.getTargets().add(opp);
|
}
|
||||||
} else if (mandatory && sa.canTarget(ai)) {
|
|
||||||
sa.getTargets().add(ai);
|
// Triggers that ask to pay {X} (e.g. Depala, Pilot Exemplar).
|
||||||
}
|
if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) {
|
||||||
}
|
int manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
|
||||||
|
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave;
|
||||||
// Triggers that ask to pay {X} (e.g. Depala, Pilot Exemplar).
|
if (numCards <= 0) {
|
||||||
if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) {
|
return mandatory;
|
||||||
int manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
|
}
|
||||||
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave;
|
sa.getHostCard().setSVar("PayX", Integer.toString(numCards));
|
||||||
if (numCards <= 0) {
|
}
|
||||||
return mandatory;
|
|
||||||
}
|
return true;
|
||||||
sa.getHostCard().setSVar("PayX", Integer.toString(numCards));
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
return true;
|
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer) {
|
||||||
}
|
if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) {
|
||||||
|
return ComputerUtilCard.getWorstPermanentAI(valid, false, true, false, false);
|
||||||
@Override
|
} else {
|
||||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer) {
|
return ComputerUtilCard.getBestAI(valid);
|
||||||
Card chosen = ComputerUtilCard.getBestAI(valid);
|
}
|
||||||
if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) {
|
}
|
||||||
return ComputerUtilCard.getWorstAI(valid);
|
|
||||||
}
|
/* (non-Javadoc)
|
||||||
return chosen;
|
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||||
}
|
*/
|
||||||
|
@Override
|
||||||
/* (non-Javadoc)
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
Card topc = player.getZone(ZoneType.Library).get(0);
|
||||||
*/
|
|
||||||
@Override
|
// AI actions for individual cards (until this AI can be generalized)
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
if (sa.getHostCard() != null) {
|
||||||
Card topc = player.getZone(ZoneType.Library).get(0);
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Explorer's Scope")) {
|
||||||
|
// for Explorer's Scope, always put a land on the battlefield tapped
|
||||||
// AI actions for individual cards (until this AI can be generalized)
|
// (TODO: might not always be a good idea, e.g. when a land ETBing can have detrimental effects)
|
||||||
if (sa.getHostCard() != null) {
|
return true;
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Explorer's Scope")) {
|
} else if ("AlwaysConfirm".equals(sa.getParam("AILogic"))) {
|
||||||
// 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)
|
}
|
||||||
return true;
|
}
|
||||||
} else if ("AlwaysConfirm".equals(sa.getParam("AILogic"))) {
|
|
||||||
return true;
|
// looks like perfect code for Delver of Secrets, but what about other cards?
|
||||||
}
|
return topc.isInstant() || topc.isSorcery();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// looks like perfect code for Delver of Secrets, but what about other cards?
|
|
||||||
return topc.isInstant() || topc.isSorcery();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,117 +1,145 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.game.card.Card;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.player.Player;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.util.MyRandom;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
import java.util.List;
|
import forge.util.MyRandom;
|
||||||
import java.util.Random;
|
|
||||||
|
import java.util.List;
|
||||||
public class DigUntilAi extends SpellAbilityAi {
|
|
||||||
|
public class DigUntilAi extends SpellAbilityAi {
|
||||||
@Override
|
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
@Override
|
||||||
Card source = sa.getHostCard();
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
double chance = .4; // 40 percent chance with instant speed stuff
|
Card source = sa.getHostCard();
|
||||||
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
chance = .667; // 66.7% chance for sorcery speed (since it will
|
double chance = .4; // 40 percent chance with instant speed stuff
|
||||||
// never activate EOT)
|
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||||
}
|
chance = .667; // 66.7% chance for sorcery speed (since it will
|
||||||
final Random r = MyRandom.getRandom();
|
// never activate EOT)
|
||||||
final boolean randomReturn = r.nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
}
|
||||||
|
// if we don't use anything now, we wasted our opportunity.
|
||||||
Player libraryOwner = ai;
|
if ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN))
|
||||||
Player opp = ai.getOpponent();
|
&& (!ai.getGame().getPhaseHandler().isPlayerTurn(ai))) {
|
||||||
|
chance = 1;
|
||||||
if (sa.usesTargeting()) {
|
}
|
||||||
sa.resetTargets();
|
|
||||||
if (!sa.canTarget(opp)) {
|
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||||
return false;
|
|
||||||
} else {
|
Player libraryOwner = ai;
|
||||||
sa.getTargets().add(opp);
|
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
}
|
|
||||||
libraryOwner = opp;
|
if ("DontMillSelf".equals(logic)) {
|
||||||
} else {
|
// A card that digs for specific things and puts everything revealed before it into graveyard
|
||||||
if (sa.hasParam("Valid")) {
|
// (e.g. Hermit Druid) - don't use it to mill itself and also make sure there's enough playable
|
||||||
final String valid = sa.getParam("Valid");
|
// material in the library after using it several times.
|
||||||
if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid.split(","), source.getController(), source, sa).isEmpty()) {
|
// TODO: maybe this should happen for any DigUntil SA with RevealedDestination$ Graveyard?
|
||||||
return false;
|
if (ai.getCardsIn(ZoneType.Library).size() < 20) {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
}
|
if ("Land.Basic".equals(sa.getParam("Valid"))
|
||||||
|
&& !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS_PRODUCING_MANA).isEmpty()) {
|
||||||
final String num = sa.getParam("Amount");
|
// We already have a mana-producing land in hand, so bail
|
||||||
if ((num != null) && num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
|
// until opponent's end of turn phase!
|
||||||
// Set PayX here to maximum value.
|
// But we still want more (and want to fill grave) if nothing better to do then
|
||||||
if (!(sa instanceof AbilitySub) || source.getSVar("PayX").equals("")) {
|
// This is important for Replenish/Living Death type decks
|
||||||
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
if (!((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN))
|
||||||
if (numCards <= 0) {
|
&& (!ai.getGame().getPhaseHandler().isPlayerTurn(ai)))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
source.setSVar("PayX", Integer.toString(numCards));
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
// return false if nothing to dig into
|
sa.resetTargets();
|
||||||
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
|
if (!sa.canTarget(opp)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
} else {
|
||||||
|
sa.getTargets().add(opp);
|
||||||
return randomReturn;
|
}
|
||||||
}
|
libraryOwner = opp;
|
||||||
|
} else {
|
||||||
@Override
|
if (sa.hasParam("Valid")) {
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
final String valid = sa.getParam("Valid");
|
||||||
|
if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid.split(","), source.getController(), source, sa).isEmpty()) {
|
||||||
if (sa.usesTargeting()) {
|
return false;
|
||||||
sa.resetTargets();
|
}
|
||||||
if (sa.isCurse()) {
|
}
|
||||||
for (Player opp : ai.getOpponents()) {
|
}
|
||||||
if (sa.canTarget(opp)) {
|
|
||||||
sa.getTargets().add(opp);
|
final String num = sa.getParam("Amount");
|
||||||
break;
|
if ((num != null) && num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
|
||||||
}
|
// Set PayX here to maximum value.
|
||||||
}
|
if (!(sa instanceof AbilitySub) || source.getSVar("PayX").equals("")) {
|
||||||
if (mandatory && sa.getTargets().isEmpty() && sa.canTarget(ai)) {
|
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
sa.getTargets().add(ai);
|
if (numCards <= 0) {
|
||||||
}
|
return false;
|
||||||
} else {
|
}
|
||||||
if (sa.canTarget(ai)) {
|
source.setSVar("PayX", Integer.toString(numCards));
|
||||||
sa.getTargets().add(ai);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
// return false if nothing to dig into
|
||||||
|
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
return randomReturn;
|
||||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
}
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (sa.hasParam("AILogic")) {
|
|
||||||
final String logic = sa.getParam("AILogic");
|
if (sa.usesTargeting()) {
|
||||||
if ("OathOfDruids".equals(logic)) {
|
sa.resetTargets();
|
||||||
final List<Card> creaturesInLibrary =
|
if (sa.isCurse()) {
|
||||||
CardLists.filter(player.getCardsIn(ZoneType.Library), CardPredicates.Presets.CREATURES);
|
for (Player opp : ai.getOpponents()) {
|
||||||
final List<Card> creaturesInBattlefield =
|
if (sa.canTarget(opp)) {
|
||||||
CardLists.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
sa.getTargets().add(opp);
|
||||||
// if there are at least 3 creatures in library,
|
break;
|
||||||
// or none in play with one in library, oath
|
}
|
||||||
return creaturesInLibrary.size() > 2
|
}
|
||||||
|| (creaturesInBattlefield.size() == 0 && creaturesInLibrary.size() > 0);
|
if (mandatory && sa.getTargets().isEmpty() && sa.canTarget(ai)) {
|
||||||
|
sa.getTargets().add(ai);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
return true;
|
if (sa.canTarget(ai)) {
|
||||||
}
|
sa.getTargets().add(ai);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
|
if (sa.hasParam("AILogic")) {
|
||||||
|
final String logic = sa.getParam("AILogic");
|
||||||
|
if ("OathOfDruids".equals(logic)) {
|
||||||
|
final List<Card> creaturesInLibrary =
|
||||||
|
CardLists.filter(player.getCardsIn(ZoneType.Library), CardPredicates.Presets.CREATURES);
|
||||||
|
final List<Card> creaturesInBattlefield =
|
||||||
|
CardLists.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
||||||
|
// if there are at least 3 creatures in library,
|
||||||
|
// or none in play with one in library, oath
|
||||||
|
return creaturesInLibrary.size() > 2
|
||||||
|
|| (creaturesInBattlefield.size() == 0 && creaturesInLibrary.size() > 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,192 +1,222 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.game.card.Card;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.Card;
|
import forge.game.cost.Cost;
|
||||||
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.player.PlayerActionConfirmMode;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
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 forge.util.MyRandom;
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
public class DiscardAi extends SpellAbilityAi {
|
||||||
|
|
||||||
public class DiscardAi extends SpellAbilityAi {
|
@Override
|
||||||
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
@Override
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
final Card source = sa.getHostCard();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
final Card source = sa.getHostCard();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
final Cost abCost = sa.getPayCosts();
|
|
||||||
|
if (abCost != null) {
|
||||||
if (abCost != null) {
|
// AI currently disabled for these costs
|
||||||
// AI currently disabled for these costs
|
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
return false;
|
||||||
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));
|
||||||
return MyRandom.getRandom().nextFloat() < (1.0 / (1 + hand));
|
}
|
||||||
}
|
|
||||||
|
if (aiLogic.equals("VolrathsShapeshifter")) {
|
||||||
final boolean humanHasHand = ai.getOpponent().getCardsIn(ZoneType.Hand).size() > 0;
|
return SpecialCardAi.VolrathsShapeshifter.consider(ai, sa);
|
||||||
|
}
|
||||||
if (tgt != null) {
|
|
||||||
if (!discardTargetAI(ai, sa)) {
|
final boolean humanHasHand = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).size() > 0;
|
||||||
return false;
|
|
||||||
}
|
if (tgt != null) {
|
||||||
} else {
|
if (!discardTargetAI(ai, sa)) {
|
||||||
// TODO: Add appropriate restrictions
|
return false;
|
||||||
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
}
|
||||||
|
} else {
|
||||||
if (players.size() == 1) {
|
// TODO: Add appropriate restrictions
|
||||||
if (players.get(0) == ai) {
|
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
// the ai should only be using something like this if he has
|
|
||||||
// few cards in hand,
|
if (players.size() == 1) {
|
||||||
// cards like this better have a good drawback to be in the
|
if (players.get(0) == ai) {
|
||||||
// AIs deck
|
// the ai should only be using something like this if he has
|
||||||
} else {
|
// few cards in hand,
|
||||||
// defined to the human, so that's fine as long the human
|
// cards like this better have a good drawback to be in the
|
||||||
// has cards
|
// AIs deck
|
||||||
if (!humanHasHand) {
|
} else {
|
||||||
return false;
|
// defined to the human, so that's fine as long the human
|
||||||
}
|
// has cards
|
||||||
}
|
if (!humanHasHand) {
|
||||||
} else {
|
return false;
|
||||||
// Both players discard, any restrictions?
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
// Both players discard, any restrictions?
|
||||||
if (sa.hasParam("NumCards")) {
|
}
|
||||||
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
}
|
||||||
// Set PayX here to maximum value.
|
|
||||||
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getOpponent()
|
if (sa.hasParam("NumCards")) {
|
||||||
.getCardsIn(ZoneType.Hand).size());
|
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||||
if (cardsToDiscard < 1) {
|
// Set PayX here to maximum value.
|
||||||
return false;
|
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ComputerUtil.getOpponentFor(ai)
|
||||||
}
|
.getCardsIn(ZoneType.Hand).size());
|
||||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
if (cardsToDiscard < 1) {
|
||||||
} else {
|
return false;
|
||||||
if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) {
|
}
|
||||||
return false;
|
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||||
}
|
} else {
|
||||||
}
|
if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
// TODO: Implement support for Discard AI for cards with AnyNumber set to true.
|
}
|
||||||
|
}
|
||||||
// Don't use draw abilities before main 2 if possible
|
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
// TODO: Improve support for Discard AI for cards with AnyNumber set to true.
|
||||||
&& !sa.hasParam("ActivationPhases")) {
|
if (sa.hasParam("AnyNumber")) {
|
||||||
return false;
|
if ("DiscardUncastableAndExcess".equals(aiLogic)) {
|
||||||
}
|
final CardCollectionView inHand = ai.getCardsIn(ZoneType.Hand);
|
||||||
|
final int numLandsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size();
|
||||||
// Don't tap creatures that may be able to block
|
int numDiscard = 0;
|
||||||
if (ComputerUtil.waitForBlocking(sa)) {
|
int numOppInHand = 0;
|
||||||
return false;
|
for (Player p : ai.getGame().getPlayers()) {
|
||||||
}
|
if (p.getCardsIn(ZoneType.Hand).size() > numOppInHand) {
|
||||||
|
numOppInHand = p.getCardsIn(ZoneType.Hand).size();
|
||||||
final Random r = MyRandom.getRandom();
|
}
|
||||||
boolean randomReturn = r.nextFloat() <= Math.pow(0.9, sa.getActivationsThisTurn());
|
}
|
||||||
|
for (Card c : inHand) {
|
||||||
// some other variables here, like handsize vs. maxHandSize
|
if (c.equals(sa.getHostCard())) { continue; }
|
||||||
|
if (c.hasSVar("DoNotDiscardIfAble") || c.hasSVar("IsReanimatorCard")) { continue; }
|
||||||
return randomReturn;
|
if (c.isCreature() && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getSpellPermanent(), ai)) {
|
||||||
} // discardCanPlayAI()
|
numDiscard++;
|
||||||
|
}
|
||||||
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
|
if ((c.isLand() && numLandsOTB >= 5) || (c.getFirstSpellAbility() != null && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getFirstSpellAbility(), ai))) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
if (numDiscard + 1 <= numOppInHand) {
|
||||||
Player opp = ai.getOpponent();
|
numDiscard++;
|
||||||
if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) {
|
}
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
if (tgt != null) {
|
if (numDiscard == 0) {
|
||||||
if (sa.canTarget(opp)) {
|
return false;
|
||||||
sa.getTargets().add(opp);
|
}
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false;
|
// Don't use draw abilities before main 2 if possible
|
||||||
} // discardTargetAI()
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||||
|
&& !sa.hasParam("ActivationPhases")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@Override
|
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
// Don't tap creatures that may be able to block
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
if (ComputerUtil.waitForBlocking(sa)) {
|
||||||
if (tgt != null) {
|
return false;
|
||||||
Player opp = ai.getOpponent();
|
}
|
||||||
if (!discardTargetAI(ai, sa)) {
|
|
||||||
if (mandatory && sa.canTarget(opp)) {
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(0.9, sa.getActivationsThisTurn());
|
||||||
sa.getTargets().add(opp);
|
|
||||||
} else if (mandatory && sa.canTarget(ai)) {
|
// some other variables here, like handsize vs. maxHandSize
|
||||||
sa.getTargets().add(ai);
|
|
||||||
} else {
|
return randomReturn;
|
||||||
return false;
|
} // discardCanPlayAI()
|
||||||
}
|
|
||||||
}
|
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
|
||||||
} else {
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (sa.hasParam("AILogic")) {
|
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
if ("AtLeast2".equals(sa.getParam("AILogic"))) {
|
if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) {
|
||||||
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
return false;
|
||||||
if (players.isEmpty() || players.get(0).getCardsIn(ZoneType.Hand).size() < 2) {
|
}
|
||||||
return false;
|
if (tgt != null) {
|
||||||
}
|
if (sa.canTarget(opp)) {
|
||||||
}
|
sa.getTargets().add(opp);
|
||||||
}
|
return true;
|
||||||
if ("X".equals(sa.getParam("RevealNumber")) && sa.getHostCard().getSVar("X").equals("Count$xPaid")) {
|
}
|
||||||
// Set PayX here to maximum value.
|
}
|
||||||
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getOpponent()
|
return false;
|
||||||
.getCardsIn(ZoneType.Hand).size());
|
} // discardTargetAI()
|
||||||
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@Override
|
||||||
return true;
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
} // discardTrigger()
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
if (tgt != null) {
|
||||||
@Override
|
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
if (!discardTargetAI(ai, sa)) {
|
||||||
// Drawback AI improvements
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
// if parent draws cards, make sure cards in hand + cards drawn > 0
|
sa.getTargets().add(opp);
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
} else if (mandatory && sa.canTarget(ai)) {
|
||||||
if (tgt != null) {
|
sa.getTargets().add(ai);
|
||||||
return discardTargetAI(ai, sa);
|
} else {
|
||||||
}
|
return false;
|
||||||
// TODO: check for some extra things
|
}
|
||||||
return true;
|
}
|
||||||
} // discardCheckDrawbackAI()
|
} else {
|
||||||
|
if (sa.hasParam("AILogic")) {
|
||||||
|
if ("AtLeast2".equals(sa.getParam("AILogic"))) {
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||||
if ( mode == PlayerActionConfirmMode.Random ) { //
|
if (players.isEmpty() || players.get(0).getCardsIn(ZoneType.Hand).size() < 2) {
|
||||||
// TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards
|
return false;
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
return super.confirmAction(player, sa, mode, message);
|
}
|
||||||
}
|
if ("X".equals(sa.getParam("RevealNumber")) && sa.getHostCard().getSVar("X").equals("Count$xPaid")) {
|
||||||
}
|
// Set PayX here to maximum value.
|
||||||
|
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ComputerUtil.getOpponentFor(ai)
|
||||||
|
.getCardsIn(ZoneType.Hand).size());
|
||||||
|
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} // discardTrigger()
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
|
// Drawback AI improvements
|
||||||
|
// if parent draws cards, make sure cards in hand + cards drawn > 0
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
if (tgt != null) {
|
||||||
|
return discardTargetAI(ai, sa);
|
||||||
|
}
|
||||||
|
// TODO: check for some extra things
|
||||||
|
return true;
|
||||||
|
} // discardCheckDrawbackAI()
|
||||||
|
|
||||||
|
|
||||||
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
|
if ( mode == PlayerActionConfirmMode.Random ) { //
|
||||||
|
// TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.confirmAction(player, sa, mode, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,91 +1,90 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.player.Player;
|
import forge.game.card.Card;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.MyRandom;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.util.MyRandom;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
import java.util.List;
|
||||||
|
|
||||||
public class DrainManaAi extends SpellAbilityAi {
|
public class DrainManaAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(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 TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Player opp = ai.getOpponent();
|
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
final Random r = MyRandom.getRandom();
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
|
||||||
|
if (tgt == null) {
|
||||||
if (tgt == null) {
|
// assume we are looking to tap human's stuff
|
||||||
// assume we are looking to tap human's stuff
|
// TODO - check for things with untap abilities, and don't tap
|
||||||
// TODO - check for things with untap abilities, and don't tap
|
// those.
|
||||||
// those.
|
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
|
||||||
|
if (!defined.contains(opp)) {
|
||||||
if (!defined.contains(opp)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
sa.resetTargets();
|
||||||
sa.resetTargets();
|
sa.getTargets().add(opp);
|
||||||
sa.getTargets().add(opp);
|
}
|
||||||
}
|
|
||||||
|
return randomReturn;
|
||||||
return randomReturn;
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
final Player opp = ai.getOpponent();
|
|
||||||
|
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 true;
|
} 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)) {
|
||||||
if (!defined.contains(opp)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return true;
|
||||||
return true;
|
} else {
|
||||||
} else {
|
sa.resetTargets();
|
||||||
sa.resetTargets();
|
sa.getTargets().add(opp);
|
||||||
sa.getTargets().add(opp);
|
}
|
||||||
}
|
|
||||||
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
// AI cannot use this properly until he can use SAs during Humans turn
|
||||||
// AI cannot use this properly until he can use SAs during Humans turn
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final Card source = sa.getHostCard();
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
|
boolean randomReturn = true;
|
||||||
boolean randomReturn = true;
|
|
||||||
|
if (tgt == null) {
|
||||||
if (tgt == null) {
|
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 false;
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
sa.resetTargets();
|
||||||
sa.resetTargets();
|
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||||
sa.getTargets().add(ai.getOpponent());
|
}
|
||||||
}
|
|
||||||
|
return randomReturn;
|
||||||
return randomReturn;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -26,10 +26,7 @@ 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;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.*;
|
||||||
import forge.game.cost.CostDiscard;
|
|
||||||
import forge.game.cost.CostPart;
|
|
||||||
import forge.game.cost.PaymentDecision;
|
|
||||||
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;
|
||||||
@@ -86,7 +83,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
|
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
|
||||||
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source)) {
|
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +123,16 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
|
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
|
||||||
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
|
if (logic.startsWith("LifeLessThan.")) {
|
||||||
|
// LifeLessThan logic presupposes activation as soon as possible in an
|
||||||
|
// attempt to save the AI from dying
|
||||||
|
return true;
|
||||||
|
} else if (logic.equals("AlwaysAtOppEOT")) {
|
||||||
|
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai);
|
||||||
|
}
|
||||||
|
|
||||||
// Don't use draw abilities before main 2 if possible
|
// Don't use draw abilities before main 2 if possible
|
||||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||||
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||||
@@ -170,7 +177,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
int numHand = ai.getCardsIn(ZoneType.Hand).size();
|
int numHand = ai.getCardsIn(ZoneType.Hand).size();
|
||||||
if ("Jace, Vryn's Prodigy".equals(sourceName) && ai.getCardsIn(ZoneType.Graveyard).size() > 3) {
|
if ("Jace, Vryn's Prodigy".equals(sourceName) && ai.getCardsIn(ZoneType.Graveyard).size() > 3) {
|
||||||
return CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS,
|
return CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS,
|
||||||
CardPredicates.isType("Jace")).size() <= 0;
|
CardPredicates.isType("Jace")).size() <= 0;
|
||||||
}
|
}
|
||||||
if (source.isSpell() && ai.getCardsIn(ZoneType.Hand).contains(source)) {
|
if (source.isSpell() && ai.getCardsIn(ZoneType.Hand).contains(source)) {
|
||||||
@@ -204,6 +211,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final boolean drawback = sa.getParent() != null;
|
final boolean drawback = sa.getParent() != null;
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
int computerHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
int computerHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
||||||
final int computerLibrarySize = ai.getCardsIn(ZoneType.Library).size();
|
final int computerLibrarySize = ai.getCardsIn(ZoneType.Library).size();
|
||||||
@@ -240,13 +248,40 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logic for cards that require special handling
|
if (num != null && num.equals("ChosenX")) {
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.getSVar("X").equals("XChoice")) {
|
||||||
if ("YawgmothsBargain".equals(sa.getParam("AILogic"))) {
|
// Draw up to max hand size but leave at least 3 in library
|
||||||
return SpecialCardAi.YawgmothsBargain.consider(ai, sa);
|
numCards = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
|
||||||
|
|
||||||
|
if (sa.getPayCosts() != null) {
|
||||||
|
if (sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) {
|
||||||
|
// [Necrologia, Pay X Life : Draw X Cards]
|
||||||
|
// Don't draw more than what's "safe" and don't risk a near death experience
|
||||||
|
// Maybe would be better to check for "serious danger" and take more risk?
|
||||||
|
while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) {
|
||||||
|
numCards--;
|
||||||
|
}
|
||||||
|
} else if (sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
|
||||||
|
// [e.g. Krav, the Unredeemed and other cases which say "Sacrifice X creatures: draw X cards]
|
||||||
|
// TODO: Add special logic to limit/otherwise modify the ChosenX value here
|
||||||
|
|
||||||
|
// Skip this ability if nothing is to be chosen for sacrifice
|
||||||
|
if (numCards <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sa.setSVar("ChosenX", Integer.toString(numCards));
|
||||||
|
source.setSVar("ChosenX", Integer.toString(numCards));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logic for cards that require special handling
|
||||||
|
if ("YawgmothsBargain".equals(logic)) {
|
||||||
|
return SpecialCardAi.YawgmothsBargain.consider(ai, sa);
|
||||||
|
}
|
||||||
|
|
||||||
// Generic logic for all cards that do not need any special handling
|
// Generic logic for all cards that do not need any special handling
|
||||||
|
|
||||||
// TODO: if xPaid and one of the below reasons would fail, instead of
|
// TODO: if xPaid and one of the below reasons would fail, instead of
|
||||||
@@ -312,6 +347,13 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// we're trying to save ourselves from death
|
||||||
|
// (e.g. Bargain), so target the opp anyway
|
||||||
|
if (logic.startsWith("LifeLessThan.")) {
|
||||||
|
int threshold = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
|
||||||
|
sa.getTargets().add(oppA);
|
||||||
|
return ai.getLife() < threshold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean aiTarget = sa.canTarget(ai);
|
boolean aiTarget = sa.canTarget(ai);
|
||||||
|
|||||||
@@ -1,284 +1,339 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
import forge.ai.SpecialCardAi;
|
import forge.ai.SpecialCardAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.ai.SpellApiToAi;
|
import forge.ai.SpellApiToAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GlobalRuleChange;
|
import forge.game.GlobalRuleChange;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.*;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.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.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
public class EffectAi extends SpellAbilityAi {
|
public class EffectAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(final Player ai,final SpellAbility sa) {
|
protected boolean canPlayAI(final Player ai,final SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final Random r = MyRandom.getRandom();
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= .6667;
|
||||||
boolean randomReturn = r.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();
|
||||||
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 false;
|
}
|
||||||
}
|
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 false;
|
}
|
||||||
}
|
randomReturn = true;
|
||||||
randomReturn = true;
|
} else if (logic.equals("KeepOppCreatsLandsTapped")) {
|
||||||
} else if (logic.equals("Fog")) {
|
for (Player opp : ai.getOpponents()) {
|
||||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
boolean worthHolding = false;
|
||||||
return false;
|
CardCollectionView oppCreatsLands = CardLists.filter(opp.getCardsIn(ZoneType.Battlefield),
|
||||||
}
|
Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.Presets.CREATURES));
|
||||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
CardCollectionView oppCreatsLandsTapped = CardLists.filter(oppCreatsLands, CardPredicates.Presets.TAPPED);
|
||||||
return false;
|
|
||||||
}
|
if (oppCreatsLandsTapped.size() >= 3 || oppCreatsLands.size() == oppCreatsLandsTapped.size()) {
|
||||||
if (!game.getStack().isEmpty()) {
|
worthHolding = true;
|
||||||
return false;
|
}
|
||||||
}
|
if (!worthHolding) {
|
||||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
randomReturn = true;
|
||||||
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
}
|
||||||
return false;
|
} else if (logic.equals("Fog")) {
|
||||||
}
|
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
return false;
|
||||||
if (tgt != null) {
|
}
|
||||||
sa.resetTargets();
|
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
if (tgt.canOnlyTgtOpponent()) {
|
return false;
|
||||||
boolean canTgt = false;
|
}
|
||||||
|
if (!game.getStack().isEmpty()) {
|
||||||
for (Player opp2 : ai.getOpponents()) {
|
return false;
|
||||||
if (sa.canTarget(opp2)) {
|
}
|
||||||
sa.getTargets().add(opp2);
|
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
||||||
canTgt = true;
|
return false;
|
||||||
break;
|
}
|
||||||
}
|
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
if (!canTgt) {
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
return false;
|
if (tgt != null) {
|
||||||
}
|
sa.resetTargets();
|
||||||
} else {
|
if (tgt.canOnlyTgtOpponent()) {
|
||||||
List<Card> list = game.getCombat().getAttackers();
|
boolean canTgt = false;
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
|
||||||
list = CardLists.getTargetableCards(list, sa);
|
for (Player opp2 : ai.getOpponents()) {
|
||||||
Card target = ComputerUtilCard.getBestCreatureAI(list);
|
if (sa.canTarget(opp2)) {
|
||||||
if (target == null) {
|
sa.getTargets().add(opp2);
|
||||||
return false;
|
canTgt = true;
|
||||||
}
|
break;
|
||||||
sa.getTargets().add(target);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
randomReturn = true;
|
if (!canTgt) {
|
||||||
} else if (logic.equals("ChainVeil")) {
|
return false;
|
||||||
if (!phase.isPlayerTurn(ai) || !phase.getPhase().equals(PhaseType.MAIN2)
|
}
|
||||||
|| CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Planeswalker").isEmpty()) {
|
} else {
|
||||||
return false;
|
List<Card> list = game.getCombat().getAttackers();
|
||||||
}
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||||
randomReturn = true;
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
} else if (logic.equals("SpellCopy")) {
|
Card target = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
// fetch Instant or Sorcery and AI has reason to play this turn
|
if (target == null) {
|
||||||
// does not try to get itself
|
return false;
|
||||||
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
|
}
|
||||||
@Override
|
sa.getTargets().add(target);
|
||||||
public boolean apply(final Card c) {
|
}
|
||||||
return (c.isInstant() || c.isSorcery()) && c != sa.getHostCard() && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c);
|
}
|
||||||
}
|
randomReturn = true;
|
||||||
});
|
} else if (logic.equals("ChainVeil")) {
|
||||||
|
if (!phase.isPlayerTurn(ai) || !phase.getPhase().equals(PhaseType.MAIN2)
|
||||||
if(count == 0) {
|
|| CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Planeswalker").isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
randomReturn = true;
|
||||||
randomReturn = true;
|
} else if (logic.equals("SpellCopy")) {
|
||||||
} else if (logic.equals("NarsetRebound")) {
|
// fetch Instant or Sorcery and AI has reason to play this turn
|
||||||
// should be done in Main2, but it might broke for other cards
|
// does not try to get itself
|
||||||
//if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
|
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
|
||||||
// return false;
|
@Override
|
||||||
//}
|
public boolean apply(final Card c) {
|
||||||
|
return (c.isInstant() || c.isSorcery()) && c != sa.getHostCard() && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c);
|
||||||
// fetch Instant or Sorcery without Rebound and AI has reason to play this turn
|
}
|
||||||
// only need count, not the list
|
});
|
||||||
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
|
|
||||||
@Override
|
if(count == 0) {
|
||||||
public boolean apply(final Card c) {
|
return false;
|
||||||
return (c.isInstant() || c.isSorcery()) && !c.hasKeyword("Rebound") && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c);
|
}
|
||||||
}
|
|
||||||
});
|
randomReturn = true;
|
||||||
|
} else if (logic.equals("NarsetRebound")) {
|
||||||
if(count == 0) {
|
// should be done in Main2, but it might broke for other cards
|
||||||
return false;
|
//if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
}
|
// return false;
|
||||||
|
//}
|
||||||
randomReturn = true;
|
|
||||||
} else if (logic.equals("Always")) {
|
// fetch Instant or Sorcery without Rebound and AI has reason to play this turn
|
||||||
randomReturn = true;
|
// only need count, not the list
|
||||||
} else if (logic.equals("Main2")) {
|
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
|
||||||
if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
|
@Override
|
||||||
return false;
|
public boolean apply(final Card c) {
|
||||||
}
|
return (c.isInstant() || c.isSorcery()) && !c.hasKeyword(Keyword.REBOUND)
|
||||||
randomReturn = true;
|
&& ComputerUtil.hasReasonToPlayCardThisTurn(ai, c);
|
||||||
} else if (logic.equals("Evasion")) {
|
}
|
||||||
|
});
|
||||||
if (!phase.isPlayerTurn(ai)) {
|
|
||||||
return false;
|
if(count == 0) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
boolean shouldPlay = false;
|
|
||||||
|
randomReturn = true;
|
||||||
List<Card> comp = ai.getCreaturesInPlay();
|
} else if (logic.equals("Always")) {
|
||||||
|
randomReturn = true;
|
||||||
for (final Player opp : ai.getOpponents()) {
|
} else if (logic.equals("Main1")) {
|
||||||
List<Card> human = opp.getCreaturesInPlay();
|
if (phase.getPhase().isBefore(PhaseType.MAIN1)) {
|
||||||
|
return false;
|
||||||
// only count creatures that can attack or block
|
}
|
||||||
comp = CardLists.filter(comp, new Predicate<Card>() {
|
randomReturn = true;
|
||||||
@Override
|
} else if (logic.equals("Main2")) {
|
||||||
public boolean apply(final Card c) {
|
if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
return CombatUtil.canAttack(c, opp);
|
return false;
|
||||||
}
|
}
|
||||||
});
|
randomReturn = true;
|
||||||
if (comp.size() < 2) {
|
} else if (logic.equals("Evasion")) {
|
||||||
continue;
|
|
||||||
}
|
if (!phase.isPlayerTurn(ai)) {
|
||||||
final List<Card> attackers = comp;
|
return false;
|
||||||
human = CardLists.filter(human, new Predicate<Card>() {
|
}
|
||||||
@Override
|
|
||||||
public boolean apply(final Card c) {
|
boolean shouldPlay = false;
|
||||||
return CombatUtil.canBlockAtLeastOne(c, attackers);
|
|
||||||
}
|
List<Card> comp = ai.getCreaturesInPlay();
|
||||||
});
|
|
||||||
if (human.isEmpty()) {
|
for (final Player opp : ai.getOpponents()) {
|
||||||
continue;
|
List<Card> human = opp.getCreaturesInPlay();
|
||||||
}
|
|
||||||
|
// only count creatures that can attack or block
|
||||||
shouldPlay = true;
|
comp = CardLists.filter(comp, new Predicate<Card>() {
|
||||||
break;
|
@Override
|
||||||
}
|
public boolean apply(final Card c) {
|
||||||
|
return CombatUtil.canAttack(c, opp);
|
||||||
if (shouldPlay) {
|
}
|
||||||
return true;
|
});
|
||||||
}
|
if (comp.size() < 2) {
|
||||||
} else if (logic.equals("RedirectSpellDamageFromPlayer")) {
|
continue;
|
||||||
if (game.getStack().isEmpty()) {
|
}
|
||||||
return false;
|
final List<Card> attackers = comp;
|
||||||
}
|
human = CardLists.filter(human, new Predicate<Card>() {
|
||||||
boolean threatened = false;
|
@Override
|
||||||
for (final SpellAbilityStackInstance stackInst : game.getStack()) {
|
public boolean apply(final Card c) {
|
||||||
if (!stackInst.isSpell()) { continue; }
|
return CombatUtil.canBlockAtLeastOne(c, attackers);
|
||||||
SpellAbility stackSpellAbility = stackInst.getSpellAbility(true);
|
}
|
||||||
if (stackSpellAbility.getApi() == ApiType.DealDamage) {
|
});
|
||||||
final SpellAbility saTargeting = stackSpellAbility.getSATargetingPlayer();
|
if (human.isEmpty()) {
|
||||||
if (saTargeting != null && Iterables.contains(saTargeting.getTargets().getTargetPlayers(), ai)) {
|
continue;
|
||||||
threatened = true;
|
}
|
||||||
}
|
|
||||||
}
|
shouldPlay = true;
|
||||||
}
|
break;
|
||||||
randomReturn = threatened;
|
}
|
||||||
} else if (logic.equals("Prevent")) { // prevent burn spell from opponent
|
|
||||||
if (game.getStack().isEmpty()) {
|
return shouldPlay;
|
||||||
return false;
|
} else if (logic.equals("RedirectSpellDamageFromPlayer")) {
|
||||||
}
|
if (game.getStack().isEmpty()) {
|
||||||
final SpellAbility saTop = game.getStack().peekAbility();
|
return false;
|
||||||
final Card host = saTop.getHostCard();
|
}
|
||||||
if (saTop.getActivatingPlayer() != ai // from opponent
|
boolean threatened = false;
|
||||||
&& !game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention) // no prevent damage
|
for (final SpellAbilityStackInstance stackInst : game.getStack()) {
|
||||||
&& host != null && (host.isInstant() || host.isSorcery())
|
if (!stackInst.isSpell()) { continue; }
|
||||||
&& !host.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")) { // valid target
|
SpellAbility stackSpellAbility = stackInst.getSpellAbility(true);
|
||||||
final ApiType type = saTop.getApi();
|
if (stackSpellAbility.getApi() == ApiType.DealDamage) {
|
||||||
if (type == ApiType.DealDamage || type == ApiType.DamageAll) { // burn spell
|
final SpellAbility saTargeting = stackSpellAbility.getSATargetingPlayer();
|
||||||
sa.getTargets().add(host);
|
if (saTargeting != null && Iterables.contains(saTargeting.getTargets().getTargetPlayers(), ai)) {
|
||||||
return true;
|
threatened = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
} else if (logic.equals("NoGain")) {
|
randomReturn = threatened;
|
||||||
// basic logic to cancel GainLife on stack
|
} else if (logic.equals("Prevent")) { // prevent burn spell from opponent
|
||||||
if (game.getStack().isEmpty()) {
|
if (game.getStack().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final SpellAbility topStack = game.getStack().peekAbility();
|
final SpellAbility saTop = game.getStack().peekAbility();
|
||||||
if (topStack.getActivatingPlayer().isOpponentOf(ai) && topStack.getApi() == ApiType.GainLife) {
|
final Card host = saTop.getHostCard();
|
||||||
return true;
|
if (saTop.getActivatingPlayer() != ai // from opponent
|
||||||
} else {
|
&& !game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention) // no prevent damage
|
||||||
return false;
|
&& host != null && (host.isInstant() || host.isSorcery())
|
||||||
}
|
&& !host.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")) { // valid target
|
||||||
} else if (logic.equals("Fight")) {
|
final ApiType type = saTop.getApi();
|
||||||
return FightAi.canFightAi(ai, sa, 0, 0);
|
if (type == ApiType.DealDamage || type == ApiType.DamageAll) { // burn spell
|
||||||
} else if (logic.equals("Burn")) {
|
sa.getTargets().add(host);
|
||||||
// for DamageDeal sub-abilities (eg. Wild Slash, Skullcrack)
|
return true;
|
||||||
SpellAbility burn = sa.getSubAbility();
|
}
|
||||||
return SpellApiToAi.Converter.get(burn.getApi()).canPlayAIWithSubs(ai, burn);
|
}
|
||||||
} else if (logic.equals("YawgmothsWill")) {
|
return false;
|
||||||
return SpecialCardAi.YawgmothsWill.consider(ai, sa);
|
} else if (logic.equals("NoGain")) {
|
||||||
} else if (logic.startsWith("NeedCreatures")) {
|
// basic logic to cancel GainLife on stack
|
||||||
if (ai.getCreaturesInPlay().isEmpty()) {
|
if (game.getStack().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (logic.contains(":")) {
|
final SpellAbility topStack = game.getStack().peekAbility();
|
||||||
String k[] = logic.split(":");
|
if (topStack.getActivatingPlayer().isOpponentOf(ai) && topStack.getApi() == ApiType.GainLife) {
|
||||||
Integer i = Integer.valueOf(k[1]);
|
return true;
|
||||||
if (ai.getCreaturesInPlay().size() < i) {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
} else if (logic.equals("Fight")) {
|
||||||
return true;
|
return FightAi.canFightAi(ai, sa, 0, 0);
|
||||||
}
|
} else if (logic.equals("Burn")) {
|
||||||
} else { //no AILogic
|
// for DamageDeal sub-abilities (eg. Wild Slash, Skullcrack)
|
||||||
return false;
|
SpellAbility burn = sa.getSubAbility();
|
||||||
}
|
return SpellApiToAi.Converter.get(burn.getApi()).canPlayAIWithSubs(ai, burn);
|
||||||
|
} else if (logic.equals("YawgmothsWill")) {
|
||||||
if ("False".equals(sa.getParam("Stackable"))) {
|
return SpecialCardAi.YawgmothsWill.consider(ai, sa);
|
||||||
String name = sa.getParam("Name");
|
} else if (logic.startsWith("NeedCreatures")) {
|
||||||
if (name == null) {
|
if (ai.getCreaturesInPlay().isEmpty()) {
|
||||||
name = sa.getHostCard().getName() + "'s Effect";
|
return false;
|
||||||
}
|
}
|
||||||
if (sa.getActivatingPlayer().isCardInCommand(name)) {
|
if (logic.contains(":")) {
|
||||||
return false;
|
String k[] = logic.split(":");
|
||||||
}
|
Integer i = Integer.valueOf(k[1]);
|
||||||
}
|
if (ai.getCreaturesInPlay().size() < i) {
|
||||||
|
return false;
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
}
|
||||||
if (tgt != null && tgt.canTgtPlayer()) {
|
}
|
||||||
sa.resetTargets();
|
return true;
|
||||||
if (tgt.canOnlyTgtOpponent() || logic.equals("BeginningOfOppTurn")) {
|
} else if (logic.equals("CastFromGraveThisTurn")) {
|
||||||
boolean canTgt = false;
|
CardCollection list = new CardCollection(game.getCardsIn(ZoneType.Graveyard));
|
||||||
for (Player opp : ai.getOpponents()) {
|
list = CardLists.getValidCards(list, sa.getTargetRestrictions().getValidTgts(), ai, sa.getHostCard(), sa);
|
||||||
if (sa.canTarget(opp)) {
|
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) {
|
||||||
sa.getTargets().add(opp);
|
return false;
|
||||||
canTgt = true;
|
}
|
||||||
break;
|
}
|
||||||
}
|
} else { //no AILogic
|
||||||
}
|
return false;
|
||||||
return canTgt;
|
}
|
||||||
} else {
|
|
||||||
sa.getTargets().add(ai);
|
if ("False".equals(sa.getParam("Stackable"))) {
|
||||||
}
|
String name = sa.getParam("Name");
|
||||||
}
|
if (name == null) {
|
||||||
|
name = sa.getHostCard().getName() + "'s Effect";
|
||||||
return randomReturn;
|
}
|
||||||
}
|
if (sa.getActivatingPlayer().isCardInCommand(name)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
if (tgt != null && tgt.canTgtPlayer()) {
|
||||||
|
sa.resetTargets();
|
||||||
|
if (tgt.canOnlyTgtOpponent() || logic.equals("BeginningOfOppTurn")) {
|
||||||
|
boolean canTgt = false;
|
||||||
|
for (Player opp : ai.getOpponents()) {
|
||||||
|
if (sa.canTarget(opp)) {
|
||||||
|
sa.getTargets().add(opp);
|
||||||
|
canTgt = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return canTgt;
|
||||||
|
} else {
|
||||||
|
sa.getTargets().add(ai);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return randomReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
|
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
|
// E.g. Nova Pentacle
|
||||||
|
if (aiLogic.equals("RedirectFromOppToCreature")) {
|
||||||
|
// try to target the opponent's best targetable permanent, if able
|
||||||
|
CardCollection oppPerms = CardLists.getValidCards(aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield), sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
||||||
|
if (!oppPerms.isEmpty()) {
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(ComputerUtilCard.getBestAI(oppPerms));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mandatory) {
|
||||||
|
// try to target the AI's worst targetable permanent, if able
|
||||||
|
CardCollection aiPerms = CardLists.getValidCards(aiPlayer.getCardsIn(ZoneType.Battlefield), sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
||||||
|
if (!aiPerms.isEmpty()) {
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(aiPerms));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,124 +1,124 @@
|
|||||||
/*
|
/*
|
||||||
* Forge: Play Magic: the Gathering.
|
* Forge: Play Magic: the Gathering.
|
||||||
* Copyright (C) 2011 Forge Team
|
* Copyright (C) 2011 Forge Team
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
import forge.ai.SpellAbilityAi;
|
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;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* AbilityFactoryBond class.
|
* AbilityFactoryBond class.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author Forge
|
* @author Forge
|
||||||
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
|
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
|
||||||
*/
|
*/
|
||||||
public final class EncodeAi extends SpellAbilityAi {
|
public final class EncodeAi extends SpellAbilityAi {
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* bondCanPlayAI.
|
* bondCanPlayAI.
|
||||||
* </p>
|
* </p>
|
||||||
* @param sa
|
* @param sa
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @param af
|
* @param af
|
||||||
* a {@link forge.game.ability.AbilityFactory} object.
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
*
|
*
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see forge.ai.SpellAbilityAi#confirmAction(forge.game.player.Player,
|
* @see forge.ai.SpellAbilityAi#confirmAction(forge.game.player.Player,
|
||||||
* forge.game.spellability.SpellAbility,
|
* forge.game.spellability.SpellAbility,
|
||||||
* forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
* forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
// only try to encode if there is a creature it can be used on
|
// only try to encode if there is a creature it can be used on
|
||||||
return chooseCard(player, player.getCreaturesInPlay(), true) != null;
|
return chooseCard(player, player.getCreaturesInPlay(), true) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
|
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
|
||||||
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
|
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
|
||||||
* forge.game.player.Player)
|
* forge.game.player.Player)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||||
return chooseCard(ai, options, isOptional);
|
return chooseCard(ai, options, isOptional);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Card chooseCard(final Player ai, Iterable<Card> list, boolean isOptional) {
|
private Card chooseCard(final Player ai, Iterable<Card> list, boolean isOptional) {
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
// final String logic = sa.getParam("AILogic");
|
// final String logic = sa.getParam("AILogic");
|
||||||
// if (logic == null) {
|
// if (logic == null) {
|
||||||
final List<Card> attackers = CardLists.filter(list, new Predicate<Card>() {
|
final List<Card> attackers = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return ComputerUtilCombat.canAttackNextTurn(c);
|
return ComputerUtilCombat.canAttackNextTurn(c);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
final List<Card> unblockables = CardLists.filter(attackers, new Predicate<Card>() {
|
final List<Card> unblockables = CardLists.filter(attackers, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
boolean canAttackOpponent = false;
|
boolean canAttackOpponent = false;
|
||||||
for (Player opp : ai.getOpponents()) {
|
for (Player opp : ai.getOpponents()) {
|
||||||
if (CombatUtil.canAttack(c, opp) && !CombatUtil.canBeBlocked(c, opp)) {
|
if (CombatUtil.canAttack(c, opp) && !CombatUtil.canBeBlocked(c, opp)) {
|
||||||
canAttackOpponent = true;
|
canAttackOpponent = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return canAttackOpponent;
|
return canAttackOpponent;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!unblockables.isEmpty()) {
|
if (!unblockables.isEmpty()) {
|
||||||
choice = ComputerUtilCard.getBestAI(unblockables);
|
choice = ComputerUtilCard.getBestAI(unblockables);
|
||||||
} else if (!attackers.isEmpty()) {
|
} else if (!attackers.isEmpty()) {
|
||||||
choice = ComputerUtilCard.getBestAI(attackers);
|
choice = ComputerUtilCard.getBestAI(attackers);
|
||||||
} else if (!isOptional) {
|
} else if (!isOptional) {
|
||||||
choice = ComputerUtilCard.getBestAI(list);
|
choice = ComputerUtilCard.getBestAI(list);
|
||||||
}
|
}
|
||||||
// }
|
// }
|
||||||
return choice;
|
return choice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Write javadoc for this type.
|
* TODO: Write javadoc for this type.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class EndTurnAi extends SpellAbilityAi {
|
public class EndTurnAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
return mandatory;
|
return mandatory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { return false; }
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { return false; }
|
||||||
|
|
||||||
/* (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 boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
63
forge-ai/src/main/java/forge/ai/ability/ExploreAi.java
Normal file
63
forge-ai/src/main/java/forge/ai/ability/ExploreAi.java
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.*;
|
||||||
|
import forge.game.card.*;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
public class ExploreAi extends SpellAbilityAi {
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
// Explore with a target (e.g. Enter the Unknown)
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
|
Card bestCreature = ComputerUtilCard.getBestCreatureAI(aiPlayer.getCardsIn(ZoneType.Battlefield));
|
||||||
|
if (bestCreature == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(bestCreature);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Card shouldPutInGraveyard(CardCollection top, Player ai) {
|
||||||
|
int predictedMana = ComputerUtilMana.getAvailableManaSources(ai, false).size();
|
||||||
|
CardCollectionView cardsOTB = ai.getCardsIn(ZoneType.Battlefield);
|
||||||
|
CardCollectionView cardsInHand = ai.getCardsIn(ZoneType.Hand);
|
||||||
|
CardCollection landsOTB = CardLists.filter(cardsOTB, CardPredicates.Presets.LANDS_PRODUCING_MANA);
|
||||||
|
CardCollection landsInHand = CardLists.filter(cardsInHand, CardPredicates.Presets.LANDS_PRODUCING_MANA);
|
||||||
|
|
||||||
|
int maxCMCDiff = 1;
|
||||||
|
int numLandsToStillNeedMore = 2;
|
||||||
|
|
||||||
|
if (ai.getController().isAI()) {
|
||||||
|
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||||
|
maxCMCDiff = aic.getIntProperty(AiProps.EXPLORE_MAX_CMC_DIFF_TO_PUT_IN_GRAVEYARD);
|
||||||
|
numLandsToStillNeedMore = aic.getIntProperty(AiProps.EXPLORE_NUM_LANDS_TO_STILL_NEED_MORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!top.isEmpty()) {
|
||||||
|
Card topCard = top.getFirst();
|
||||||
|
if (landsInHand.isEmpty() && landsOTB.size() <= numLandsToStillNeedMore) {
|
||||||
|
// We need more lands to improve our mana base, explore away the non-lands
|
||||||
|
return topCard;
|
||||||
|
}
|
||||||
|
if (topCard.getCMC() - maxCMCDiff >= predictedMana && !topCard.hasSVar("DoNotDiscardIfAble")) {
|
||||||
|
// We're not casting this in foreseeable future, put it in the graveyard
|
||||||
|
return topCard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put on top of the library (do not mark the card for placement in the graveyard)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,262 +1,271 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import forge.ai.*;
|
||||||
import java.util.Map;
|
import forge.game.ability.AbilityFactory;
|
||||||
import java.util.Random;
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.Card;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.game.card.CardCollection;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.game.card.CardLists;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.game.player.Player;
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.card.Card;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.util.MyRandom;
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.player.Player;
|
import java.util.List;
|
||||||
import forge.game.spellability.AbilitySub;
|
import java.util.Map;
|
||||||
import forge.game.spellability.SpellAbility;
|
|
||||||
import forge.game.trigger.Trigger;
|
public class FightAi extends SpellAbilityAi {
|
||||||
import forge.game.trigger.TriggerType;
|
@Override
|
||||||
import forge.util.MyRandom;
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
|
if (sa.hasParam("FightWithToughness")) {
|
||||||
public class FightAi extends SpellAbilityAi {
|
// TODO: add ailogic
|
||||||
@Override
|
return false;
|
||||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
}
|
||||||
if (sa.hasParam("FightWithToughness")) {
|
return super.checkAiLogic(ai, sa, aiLogic);
|
||||||
// TODO: add ailogic
|
}
|
||||||
return false;
|
|
||||||
}
|
@Override
|
||||||
return super.checkAiLogic(ai, sa, aiLogic);
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
}
|
sa.resetTargets();
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
@Override
|
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
// everything is defined or targeted above, can't do anything there?
|
||||||
sa.resetTargets();
|
if (sa.hasParam("Defined") && !sa.usesTargeting()) {
|
||||||
final Card source = sa.getHostCard();
|
// TODO extend Logic for cards like Arena or Grothama
|
||||||
|
return true;
|
||||||
// Get creature lists
|
}
|
||||||
CardCollectionView aiCreatures = ai.getCreaturesInPlay();
|
|
||||||
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
|
// Get creature lists
|
||||||
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
CardCollectionView aiCreatures = ai.getCreaturesInPlay();
|
||||||
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
|
||||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
||||||
|
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
||||||
// assumes the triggered card belongs to the ai
|
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||||
if (sa.hasParam("Defined")) {
|
|
||||||
Card fighter1 = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
|
// assumes the triggered card belongs to the ai
|
||||||
for (Card humanCreature : humCreatures) {
|
if (sa.hasParam("Defined")) {
|
||||||
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= fighter1.getNetPower()
|
Card fighter1 = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
|
||||||
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(fighter1)) {
|
for (Card humanCreature : humCreatures) {
|
||||||
// todo: check min/max targets; see if we picked the best
|
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= fighter1.getNetPower()
|
||||||
// matchup
|
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(fighter1)) {
|
||||||
sa.getTargets().add(humanCreature);
|
// todo: check min/max targets; see if we picked the best
|
||||||
return true;
|
// matchup
|
||||||
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
|
sa.getTargets().add(humanCreature);
|
||||||
sa.getTargets().add(humanCreature);
|
return true;
|
||||||
return true;
|
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
|
||||||
}
|
sa.getTargets().add(humanCreature);
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (sa.hasParam("TargetsFromDifferentZone")) {
|
return false; // bail at this point, otherwise the AI will overtarget and waste the activation
|
||||||
if (!(humCreatures.isEmpty() && aiCreatures.isEmpty())) {
|
}
|
||||||
for (Card humanCreature : humCreatures) {
|
|
||||||
for (Card aiCreature : aiCreatures) {
|
if (sa.hasParam("TargetsFromDifferentZone")) {
|
||||||
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
|
if (!(humCreatures.isEmpty() && aiCreatures.isEmpty())) {
|
||||||
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(aiCreature)) {
|
for (Card humanCreature : humCreatures) {
|
||||||
// todo: check min/max targets; see if we picked the
|
for (Card aiCreature : aiCreatures) {
|
||||||
// best matchup
|
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
|
||||||
sa.getTargets().add(humanCreature);
|
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(aiCreature)) {
|
||||||
sa.getTargets().add(aiCreature);
|
// todo: check min/max targets; see if we picked the
|
||||||
return true;
|
// best matchup
|
||||||
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
|
sa.getTargets().add(humanCreature);
|
||||||
sa.getTargets().add(humanCreature);
|
sa.getTargets().add(aiCreature);
|
||||||
sa.getTargets().add(aiCreature);
|
return true;
|
||||||
return true;
|
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
|
||||||
}
|
sa.getTargets().add(humanCreature);
|
||||||
}
|
sa.getTargets().add(aiCreature);
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
for (Card creature1 : humCreatures) {
|
}
|
||||||
for (Card creature2 : humCreatures) {
|
return false;
|
||||||
if (creature1.equals(creature2)) {
|
}
|
||||||
continue;
|
for (Card creature1 : humCreatures) {
|
||||||
}
|
for (Card creature2 : humCreatures) {
|
||||||
if (sa.hasParam("TargetsWithoutSameCreatureType") && creature1.sharesCreatureTypeWith(creature2)) {
|
if (creature1.equals(creature2)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ComputerUtilCombat.getDamageToKill(creature1) <= creature2.getNetPower()
|
if (sa.hasParam("TargetsWithoutSameCreatureType") && creature1.sharesCreatureTypeWith(creature2)) {
|
||||||
&& creature1.getNetPower() >= ComputerUtilCombat.getDamageToKill(creature2)) {
|
continue;
|
||||||
// todo: check min/max targets; see if we picked the best
|
}
|
||||||
// matchup
|
if (ComputerUtilCombat.getDamageToKill(creature1) <= creature2.getNetPower()
|
||||||
sa.getTargets().add(creature1);
|
&& creature1.getNetPower() >= ComputerUtilCombat.getDamageToKill(creature2)) {
|
||||||
sa.getTargets().add(creature2);
|
// todo: check min/max targets; see if we picked the best
|
||||||
return true;
|
// matchup
|
||||||
}
|
sa.getTargets().add(creature1);
|
||||||
}
|
sa.getTargets().add(creature2);
|
||||||
}
|
return true;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@Override
|
return false;
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
}
|
||||||
if (canPlayAI(ai, sa)) {
|
|
||||||
return true;
|
@Override
|
||||||
}
|
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
||||||
if (!mandatory) {
|
return checkApiLogic(aiPlayer, sa);
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
//try to make a good trade or no trade
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card source = sa.getHostCard();
|
if (canPlayAI(ai, sa)) {
|
||||||
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
return true;
|
||||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
}
|
||||||
if (humCreatures.isEmpty()) {
|
if (!mandatory) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//assumes the triggered card belongs to the ai
|
|
||||||
if (sa.hasParam("Defined")) {
|
//try to make a good trade or no trade
|
||||||
Card aiCreature = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
|
final Card source = sa.getHostCard();
|
||||||
for (Card humanCreature : humCreatures) {
|
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
||||||
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
|
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||||
&& ComputerUtilCard.evaluateCreature(humanCreature) > ComputerUtilCard.evaluateCreature(aiCreature)) {
|
if (humCreatures.isEmpty()) {
|
||||||
sa.getTargets().add(humanCreature);
|
return false;
|
||||||
return true;
|
}
|
||||||
}
|
//assumes the triggered card belongs to the ai
|
||||||
}
|
if (sa.hasParam("Defined")) {
|
||||||
for (Card humanCreature : humCreatures) {
|
Card aiCreature = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
|
||||||
if (ComputerUtilCombat.getDamageToKill(aiCreature) > humanCreature.getNetPower()) {
|
for (Card humanCreature : humCreatures) {
|
||||||
sa.getTargets().add(humanCreature);
|
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
|
||||||
return true;
|
&& ComputerUtilCard.evaluateCreature(humanCreature) > ComputerUtilCard.evaluateCreature(aiCreature)) {
|
||||||
}
|
sa.getTargets().add(humanCreature);
|
||||||
}
|
return true;
|
||||||
sa.getTargets().add(humCreatures.get(0));
|
}
|
||||||
return true;
|
}
|
||||||
}
|
for (Card humanCreature : humCreatures) {
|
||||||
return true;
|
if (ComputerUtilCombat.getDamageToKill(aiCreature) > humanCreature.getNetPower()) {
|
||||||
}
|
sa.getTargets().add(humanCreature);
|
||||||
|
return true;
|
||||||
/**
|
}
|
||||||
* Logic for evaluating fight effects
|
}
|
||||||
* @param ai controlling player
|
sa.getTargets().add(humCreatures.get(0));
|
||||||
* @param sa host SpellAbility
|
return true;
|
||||||
* @param toughness bonus to toughness
|
}
|
||||||
* @param power bonus to power
|
return true;
|
||||||
* @return true if fight effect should be played, false otherwise
|
}
|
||||||
*/
|
|
||||||
public static boolean canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) {
|
/**
|
||||||
final Card source = sa.getHostCard();
|
* Logic for evaluating fight effects
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
* @param ai controlling player
|
||||||
final AbilitySub tgtFight = sa.getSubAbility();
|
* @param sa host SpellAbility
|
||||||
final boolean isChandrasIgnition = "Chandra's Ignition".equals(sourceName); // TODO: generalize this for other "fake Fight" cases that do not target
|
* @param toughness bonus to toughness
|
||||||
if ("Savage Punch".equals(sourceName) && !ai.hasFerocious()) {
|
* @param power bonus to power
|
||||||
power = 0;
|
* @return true if fight effect should be played, false otherwise
|
||||||
toughness = 0;
|
*/
|
||||||
}
|
public static boolean canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) {
|
||||||
// Get sorted creature lists
|
final Card source = sa.getHostCard();
|
||||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
CardCollection humCreatures = ai.getOpponents().getCreaturesInPlay();
|
final AbilitySub tgtFight = sa.getSubAbility();
|
||||||
if ("Time to Feed".equals(sourceName)) { // flip sa
|
final boolean isChandrasIgnition = "Chandra's Ignition".equals(sourceName); // TODO: generalize this for other "fake Fight" cases that do not target
|
||||||
aiCreatures = CardLists.getTargetableCards(aiCreatures, tgtFight);
|
if ("Savage Punch".equals(sourceName) && !ai.hasFerocious()) {
|
||||||
aiCreatures = ComputerUtil.getSafeTargets(ai, tgtFight, aiCreatures);
|
power = 0;
|
||||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
toughness = 0;
|
||||||
} else {
|
}
|
||||||
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
|
// Get sorted creature lists
|
||||||
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||||
humCreatures = CardLists.getTargetableCards(humCreatures, tgtFight);
|
CardCollection humCreatures = ai.getOpponents().getCreaturesInPlay();
|
||||||
}
|
if ("Time to Feed".equals(sourceName)) { // flip sa
|
||||||
ComputerUtilCard.sortByEvaluateCreature(aiCreatures);
|
aiCreatures = CardLists.getTargetableCards(aiCreatures, tgtFight);
|
||||||
ComputerUtilCard.sortByEvaluateCreature(humCreatures);
|
aiCreatures = ComputerUtil.getSafeTargets(ai, tgtFight, aiCreatures);
|
||||||
if (humCreatures.isEmpty() || aiCreatures.isEmpty()) {
|
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||||
return false;
|
} else {
|
||||||
}
|
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
|
||||||
// Evaluate creature pairs
|
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
||||||
for (Card humanCreature : humCreatures) {
|
humCreatures = CardLists.getTargetableCards(humCreatures, tgtFight);
|
||||||
for (Card aiCreature : aiCreatures) {
|
}
|
||||||
if (source.isSpell()) { // heroic triggers adding counters and prowess
|
ComputerUtilCard.sortByEvaluateCreature(aiCreatures);
|
||||||
final int bonus = getSpellBonus(aiCreature);
|
ComputerUtilCard.sortByEvaluateCreature(humCreatures);
|
||||||
power += bonus;
|
if (humCreatures.isEmpty() || aiCreatures.isEmpty()) {
|
||||||
toughness += bonus;
|
return false;
|
||||||
}
|
}
|
||||||
if ("PowerDmg".equals(sa.getParam("AILogic"))) {
|
// Evaluate creature pairs
|
||||||
if (FightAi.canKill(aiCreature, humanCreature, power)) {
|
for (Card humanCreature : humCreatures) {
|
||||||
sa.getTargets().add(aiCreature);
|
for (Card aiCreature : aiCreatures) {
|
||||||
if (!isChandrasIgnition) {
|
if (source.isSpell()) { // heroic triggers adding counters and prowess
|
||||||
tgtFight.resetTargets();
|
final int bonus = getSpellBonus(aiCreature);
|
||||||
tgtFight.getTargets().add(humanCreature);
|
power += bonus;
|
||||||
}
|
toughness += bonus;
|
||||||
return true;
|
}
|
||||||
}
|
if ("PowerDmg".equals(sa.getParam("AILogic"))) {
|
||||||
} else {
|
if (FightAi.canKill(aiCreature, humanCreature, power)) {
|
||||||
if (FightAi.shouldFight(aiCreature, humanCreature, power, toughness)) {
|
sa.getTargets().add(aiCreature);
|
||||||
if ("Time to Feed".equals(sourceName)) { // flip targets
|
if (!isChandrasIgnition) {
|
||||||
final Card tmp = aiCreature;
|
tgtFight.resetTargets();
|
||||||
aiCreature = humanCreature;
|
tgtFight.getTargets().add(humanCreature);
|
||||||
humanCreature = tmp;
|
}
|
||||||
}
|
return true;
|
||||||
sa.getTargets().add(aiCreature);
|
}
|
||||||
tgtFight.resetTargets();
|
} else {
|
||||||
tgtFight.getTargets().add(humanCreature);
|
if (FightAi.shouldFight(aiCreature, humanCreature, power, toughness)) {
|
||||||
return true;
|
if ("Time to Feed".equals(sourceName)) { // flip targets
|
||||||
}
|
final Card tmp = aiCreature;
|
||||||
}
|
aiCreature = humanCreature;
|
||||||
}
|
humanCreature = tmp;
|
||||||
}
|
}
|
||||||
return false;
|
sa.getTargets().add(aiCreature);
|
||||||
}
|
tgtFight.resetTargets();
|
||||||
|
tgtFight.getTargets().add(humanCreature);
|
||||||
/**
|
return true;
|
||||||
* Compute the bonus from Heroic +1/+1 counters or Prowess
|
}
|
||||||
*/
|
}
|
||||||
private static int getSpellBonus(final Card aiCreature) {
|
}
|
||||||
for (Trigger t : aiCreature.getTriggers()) {
|
}
|
||||||
if (t.getMode() == TriggerType.SpellCast) {
|
return false;
|
||||||
final Map<String, String> params = t.getMapParams();
|
}
|
||||||
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))
|
|
||||||
&& params.containsKey("Execute")) {
|
/**
|
||||||
SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature);
|
* Compute the bonus from Heroic +1/+1 counters or Prowess
|
||||||
if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) {
|
*/
|
||||||
return AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic);
|
private static int getSpellBonus(final Card aiCreature) {
|
||||||
}
|
for (Trigger t : aiCreature.getTriggers()) {
|
||||||
break;
|
if (t.getMode() == TriggerType.SpellCast) {
|
||||||
}
|
final Map<String, String> params = t.getMapParams();
|
||||||
if ("ProwessPump".equals(params.get("Execute"))) {
|
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))
|
||||||
return 1;
|
&& params.containsKey("Execute")) {
|
||||||
}
|
SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature);
|
||||||
}
|
if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) {
|
||||||
}
|
return AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic);
|
||||||
return 0;
|
}
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
private static boolean shouldFight(Card fighter, Card opponent, int pumpAttack, int pumpDefense) {
|
if ("ProwessPump".equals(params.get("Execute"))) {
|
||||||
if (canKill(fighter, opponent, pumpAttack)) {
|
return 1;
|
||||||
if (!canKill(opponent, fighter, -pumpDefense)) { // can survive
|
}
|
||||||
return true;
|
}
|
||||||
} else {
|
}
|
||||||
final Random r = MyRandom.getRandom();
|
return 0;
|
||||||
if (r.nextInt(20)<(opponent.getCMC() - fighter.getCMC())) { // trade
|
}
|
||||||
return true;
|
|
||||||
}
|
private static boolean shouldFight(Card fighter, Card opponent, int pumpAttack, int pumpDefense) {
|
||||||
}
|
if (canKill(fighter, opponent, pumpAttack)) {
|
||||||
}
|
if (!canKill(opponent, fighter, -pumpDefense)) { // can survive
|
||||||
return false;
|
return true;
|
||||||
}
|
} else {
|
||||||
public static boolean canKill(Card fighter, Card opponent, int pumpAttack) {
|
if (MyRandom.getRandom().nextInt(20)<(opponent.getCMC() - fighter.getCMC())) { // trade
|
||||||
if (opponent.getSVar("Targeting").equals("Dies")) {
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
if (opponent.hasProtectionFrom(fighter) || !opponent.canBeDestroyed()
|
}
|
||||||
|| opponent.getShieldCount() > 0 || ComputerUtil.canRegenerate(opponent.getController(), opponent)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
if (fighter.hasKeyword("Deathtouch") || ComputerUtilCombat.getDamageToKill(opponent) <= fighter.getNetPower() + pumpAttack) {
|
public static boolean canKill(Card fighter, Card opponent, int pumpAttack) {
|
||||||
return true;
|
if (opponent.getSVar("Targeting").equals("Dies")) {
|
||||||
}
|
return true;
|
||||||
return false;
|
}
|
||||||
}
|
if (opponent.hasProtectionFrom(fighter) || !opponent.canBeDestroyed() || opponent.getShieldCount() > 0
|
||||||
}
|
|| ComputerUtil.canRegenerate(opponent.getController(), opponent)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (fighter.hasKeyword(Keyword.DEATHTOUCH)
|
||||||
|
|| ComputerUtilCombat.getDamageToKill(opponent) <= fighter.getNetPower() + pumpAttack) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,47 +1,47 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
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;
|
||||||
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;
|
||||||
|
|
||||||
public class FlipACoinAi extends SpellAbilityAi {
|
public class FlipACoinAi 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 boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
String AILogic = sa.getParam("AILogic");
|
String AILogic = sa.getParam("AILogic");
|
||||||
if (AILogic.equals("Never")) {
|
if (AILogic.equals("Never")) {
|
||||||
return false;
|
return false;
|
||||||
} else if (AILogic.equals("PhaseOut")) {
|
} else if (AILogic.equals("PhaseOut")) {
|
||||||
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (AILogic.equals("KillOrcs")) {
|
} else if (AILogic.equals("KillOrcs")) {
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
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 true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
return canPlayAI(ai, sa);
|
return canPlayAI(ai, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +1,116 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.*;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.game.Game;
|
||||||
import forge.game.Game;
|
import forge.game.GameObject;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.card.Card;
|
||||||
import forge.game.player.Player;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.keyword.Keyword;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
public class FogAi extends SpellAbilityAi {
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
/* (non-Javadoc)
|
import forge.util.Aggregates;
|
||||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
|
||||||
*/
|
import java.util.List;
|
||||||
@Override
|
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
public class FogAi extends SpellAbilityAi {
|
||||||
final Game game = ai.getGame();
|
|
||||||
// AI should only activate this during Human's Declare Blockers phase
|
/* (non-Javadoc)
|
||||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
return false;
|
*/
|
||||||
}
|
@Override
|
||||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
return false;
|
final Game game = ai.getGame();
|
||||||
}
|
final Card hostCard = sa.getHostCard();
|
||||||
|
|
||||||
// Only cast when Stack is empty, so Human uses spells/abilities first
|
// Don't cast it, if the effect is already in place
|
||||||
if (!game.getStack().isEmpty()) {
|
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't cast it, if the effect is already in place
|
// if card would be destroyed, react and use immediately if it's not own turn
|
||||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
if ((AiCardMemory.isRememberedCard(ai, hostCard, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT))
|
||||||
return false;
|
&& (!game.getStack().isEmpty())
|
||||||
}
|
&& (!game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer()))) {
|
||||||
|
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(ai, null);
|
||||||
// Cast it if life is in danger
|
if (objects.contains(hostCard)) {
|
||||||
return ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
|
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
@Override
|
}
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
|
||||||
// AI should only activate this during Human's turn
|
// Reserve mana to cast this card if it will be likely needed
|
||||||
boolean chance;
|
if (((game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer()))
|
||||||
final Game game = ai.getGame();
|
|| (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)))
|
||||||
|
&& (AiCardMemory.isMemorySetEmpty(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT))
|
||||||
// should really check if other player is attacking this player
|
&& (ComputerUtil.aiLifeInDanger(ai, false, 0))) {
|
||||||
if (ai.isOpponentOf(game.getPhaseHandler().getPlayerTurn())) {
|
((PlayerControllerAi) ai.getController()).getAi().reserveManaSources(sa, PhaseType.COMBAT_DECLARE_BLOCKERS, true);
|
||||||
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
|
AiCardMemory.rememberCard(ai, hostCard, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT);
|
||||||
} else {
|
}
|
||||||
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
|
|
||||||
}
|
// AI should only activate this during Human's Declare Blockers phase
|
||||||
|
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||||
return chance;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
@Override
|
return false;
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
}
|
||||||
final Game game = aiPlayer.getGame();
|
|
||||||
boolean chance;
|
// Only cast when Stack is empty, so Human uses spells/abilities first
|
||||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer().getOpponent())) {
|
if (!game.getStack().isEmpty()) {
|
||||||
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
|
return false;
|
||||||
} else {
|
}
|
||||||
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
|
|
||||||
}
|
if ("SeriousDamage".equals(sa.getParam("AILogic")) && game.getCombat() != null) {
|
||||||
|
int dmg = 0;
|
||||||
return chance;
|
for (Card atk : game.getCombat().getAttackersOf(ai)) {
|
||||||
}
|
if (game.getCombat().isUnblocked(atk)) {
|
||||||
}
|
dmg += atk.getNetCombatDamage();
|
||||||
|
} else if (atk.hasKeyword(Keyword.TRAMPLE)) {
|
||||||
|
dmg += atk.getNetCombatDamage() - Aggregates.sum(game.getCombat().getBlockers(atk), CardPredicates.Accessors.fnGetNetToughness);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dmg > ai.getLife() / 4) {
|
||||||
|
return true;
|
||||||
|
} else if (dmg >= 5) {
|
||||||
|
return true;
|
||||||
|
} else if (ai.getLife() < ai.getStartingLife() / 3) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast it if life is in danger
|
||||||
|
return ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
|
// AI should only activate this during Human's turn
|
||||||
|
boolean chance;
|
||||||
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
|
// should really check if other player is attacking this player
|
||||||
|
if (ai.isOpponentOf(game.getPhaseHandler().getPlayerTurn())) {
|
||||||
|
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
|
||||||
|
} else {
|
||||||
|
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
final Game game = aiPlayer.getGame();
|
||||||
|
boolean chance;
|
||||||
|
if (game.getPhaseHandler().isPlayerTurn(ComputerUtil.getOpponentFor(sa.getActivatingPlayer()))) {
|
||||||
|
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
|
||||||
|
} else {
|
||||||
|
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,49 +1,50 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.game.player.Player;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
public class GameLossAi extends SpellAbilityAi {
|
|
||||||
@Override
|
public class GameLossAi extends SpellAbilityAi {
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
@Override
|
||||||
final Player opp = ai.getOpponent();
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
if (opp.cantLose()) {
|
final Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
return false;
|
if (opp.cantLose()) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
// Only one SA Lose the Game card right now, which is Door to
|
|
||||||
// Nothingness
|
// Only one SA Lose the Game card right now, which is Door to
|
||||||
|
// Nothingness
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
if (tgt != null) {
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
sa.resetTargets();
|
if (tgt != null) {
|
||||||
sa.getTargets().add(opp);
|
sa.resetTargets();
|
||||||
}
|
sa.getTargets().add(opp);
|
||||||
|
}
|
||||||
// In general, don't return true.
|
|
||||||
// But this card wins the game, I can make an exception for that
|
// In general, don't return true.
|
||||||
return true;
|
// But this card wins the game, I can make an exception for that
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
@Override
|
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
// Phage the Untouchable
|
|
||||||
// (Final Fortune would need to attach it's delayed trigger to a
|
// Phage the Untouchable
|
||||||
// specific turn, which can't be done yet)
|
// (Final Fortune would need to attach it's delayed trigger to a
|
||||||
|
// specific turn, which can't be done yet)
|
||||||
if (!mandatory && ai.getOpponent().cantLose()) {
|
|
||||||
return false;
|
if (!mandatory && ComputerUtil.getOpponentFor(ai).cantLose()) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
if (tgt != null) {
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
sa.resetTargets();
|
if (tgt != null) {
|
||||||
sa.getTargets().add(ai.getOpponent());
|
sa.resetTargets();
|
||||||
}
|
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
|
||||||
|
}
|
||||||
return true;
|
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
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 GameWinAi extends SpellAbilityAi {
|
public class GameWinAi 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 boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
if (ai.cantWin()) {
|
if (ai.cantWin()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
// 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
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.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;
|
||||||
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 HauntAi extends SpellAbilityAi {
|
public class HauntAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card card = sa.getHostCard();
|
final Card card = sa.getHostCard();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
if (sa.usesTargeting() && !card.isToken()) {
|
if (sa.usesTargeting() && !card.isToken()) {
|
||||||
final List<Card> creats = CardLists.filter(game.getCardsIn(ZoneType.Battlefield),
|
final List<Card> creats = CardLists.filter(game.getCardsIn(ZoneType.Battlefield),
|
||||||
CardPredicates.Presets.CREATURES);
|
CardPredicates.Presets.CREATURES);
|
||||||
|
|
||||||
// nothing to haunt
|
// nothing to haunt
|
||||||
if (creats.isEmpty()) {
|
if (creats.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,60 +1,60 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Write javadoc for this type.
|
* TODO: Write javadoc for this type.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class LegendaryRuleAi extends SpellAbilityAi {
|
public class LegendaryRuleAi extends SpellAbilityAi {
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
|
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
return false; // should not get here
|
return false; // should not get here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||||
// Choose a single legendary/planeswalker card to keep
|
// Choose a single legendary/planeswalker card to keep
|
||||||
Card firstOption = Iterables.getFirst(options, null);
|
Card firstOption = Iterables.getFirst(options, null);
|
||||||
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
|
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
|
||||||
|
|
||||||
if ( choosingFromPlanewalkers ) {
|
if ( choosingFromPlanewalkers ) {
|
||||||
// AI decision making - should AI compare counters?
|
// AI decision making - should AI compare counters?
|
||||||
} else {
|
} else {
|
||||||
// AI decision making - should AI compare damage and debuffs?
|
// AI decision making - should AI compare damage and debuffs?
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Can this be made more generic somehow?
|
// TODO: Can this be made more generic somehow?
|
||||||
if (firstOption.getName().equals("Dark Depths")) {
|
if (firstOption.getName().equals("Dark Depths")) {
|
||||||
Card best = firstOption;
|
Card best = firstOption;
|
||||||
for (Card c : options) {
|
for (Card c : options) {
|
||||||
if (c.getCounters(CounterType.ICE) < best.getCounters(CounterType.ICE)) {
|
if (c.getCounters(CounterType.ICE) < best.getCounters(CounterType.ICE)) {
|
||||||
best = c;
|
best = c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return best;
|
return best;
|
||||||
} else if (firstOption.getCounters(CounterType.KI) > 0) {
|
} else if (firstOption.getCounters(CounterType.KI) > 0) {
|
||||||
// Extra Rule for KI counter
|
// Extra Rule for KI counter
|
||||||
Card best = firstOption;
|
Card best = firstOption;
|
||||||
for (Card c : options) {
|
for (Card c : options) {
|
||||||
if (c.getCounters(CounterType.KI) > best.getCounters(CounterType.KI)) {
|
if (c.getCounters(CounterType.KI) > best.getCounters(CounterType.KI)) {
|
||||||
best = c;
|
best = c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
return firstOption;
|
return firstOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,93 +1,91 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.game.player.Player;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.MyRandom;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.util.MyRandom;
|
||||||
import java.util.Random;
|
|
||||||
|
public class LifeExchangeAi extends SpellAbilityAi {
|
||||||
public class LifeExchangeAi extends SpellAbilityAi {
|
|
||||||
|
/*
|
||||||
/*
|
* (non-Javadoc)
|
||||||
* (non-Javadoc)
|
*
|
||||||
*
|
* @see
|
||||||
* @see
|
* forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI
|
||||||
* forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI
|
* (forge.game.player.Player, java.util.Map,
|
||||||
* (forge.game.player.Player, java.util.Map,
|
* forge.card.spellability.SpellAbility)
|
||||||
* forge.card.spellability.SpellAbility)
|
*/
|
||||||
*/
|
@Override
|
||||||
@Override
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
final int myLife = aiPlayer.getLife();
|
||||||
final Random r = MyRandom.getRandom();
|
Player opponent = ComputerUtil.getOpponentFor(aiPlayer);
|
||||||
final int myLife = aiPlayer.getLife();
|
final int hLife = opponent.getLife();
|
||||||
Player opponent = aiPlayer.getOpponent();
|
|
||||||
final int hLife = opponent.getLife();
|
if (!aiPlayer.canGainLife()) {
|
||||||
|
return false;
|
||||||
if (!aiPlayer.canGainLife()) {
|
}
|
||||||
return false;
|
|
||||||
}
|
// prevent run-away activations - first time will always return true
|
||||||
|
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
// prevent run-away activations - first time will always return true
|
|
||||||
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
/*
|
||||||
|
* TODO - There is one card that takes two targets (Soul Conduit)
|
||||||
/*
|
* and one card that has a conditional (Psychic Transfer) that are
|
||||||
* TODO - There is one card that takes two targets (Soul Conduit)
|
* not currently handled
|
||||||
* and one card that has a conditional (Psychic Transfer) that are
|
*/
|
||||||
* not currently handled
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
*/
|
if (tgt != null) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
sa.resetTargets();
|
||||||
if (tgt != null) {
|
if (opponent.canBeTargetedBy(sa)) {
|
||||||
sa.resetTargets();
|
// never target self, that would be silly for exchange
|
||||||
if (opponent.canBeTargetedBy(sa)) {
|
sa.getTargets().add(opponent);
|
||||||
// never target self, that would be silly for exchange
|
if (!opponent.canLoseLife()) {
|
||||||
sa.getTargets().add(opponent);
|
return false;
|
||||||
if (!opponent.canLoseLife()) {
|
}
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
// if life is in danger, always activate
|
||||||
|
if ((myLife < 5) && (hLife > myLife)) {
|
||||||
// if life is in danger, always activate
|
return true;
|
||||||
if ((myLife < 5) && (hLife > myLife)) {
|
}
|
||||||
return true;
|
|
||||||
}
|
// cost includes sacrifice probably, so make sure it's worth it
|
||||||
|
chance &= (hLife > (myLife + 8));
|
||||||
// cost includes sacrifice probably, so make sure it's worth it
|
|
||||||
chance &= (hLife > (myLife + 8));
|
return ((MyRandom.getRandom().nextFloat() < .6667) && chance);
|
||||||
|
}
|
||||||
return ((r.nextFloat() < .6667) && chance);
|
|
||||||
}
|
/**
|
||||||
|
* <p>
|
||||||
/**
|
* exchangeLifeDoTriggerAINoCost.
|
||||||
* <p>
|
* </p>
|
||||||
* exchangeLifeDoTriggerAINoCost.
|
* @param sa
|
||||||
* </p>
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @param sa
|
* @param mandatory
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a boolean.
|
||||||
* @param mandatory
|
* @param af
|
||||||
* a boolean.
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
* @param af
|
*
|
||||||
* a {@link forge.game.ability.AbilityFactory} object.
|
* @return a boolean.
|
||||||
*
|
*/
|
||||||
* @return a boolean.
|
@Override
|
||||||
*/
|
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
||||||
@Override
|
final boolean mandatory) {
|
||||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
|
||||||
final boolean mandatory) {
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
if (tgt != null) {
|
||||||
Player opp = ai.getOpponent();
|
sa.resetTargets();
|
||||||
if (tgt != null) {
|
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||||
sa.resetTargets();
|
sa.getTargets().add(opp);
|
||||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
} else {
|
||||||
sa.getTargets().add(opp);
|
return false;
|
||||||
} else {
|
}
|
||||||
return false;
|
}
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
|
import forge.ai.ComputerUtilAbility;
|
||||||
|
import forge.ai.ComputerUtilCombat;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.zone.MagicStack;
|
||||||
|
|
||||||
|
public class LifeExchangeVariantAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
*
|
||||||
|
* @see
|
||||||
|
* forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI
|
||||||
|
* (forge.game.player.Player, java.util.Map,
|
||||||
|
* forge.card.spellability.SpellAbility)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
|
if ("Tree of Redemption".equals(sourceName)) {
|
||||||
|
if (!ai.canGainLife())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// someone controls "Rain of Gore" or "Sulfuric Vortex", lifegain is bad in that case
|
||||||
|
if (game.isCardInPlay("Rain of Gore") || game.isCardInPlay("Sulfuric Vortex"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// an opponent controls "Tainted Remedy", lifegain is bad in that case
|
||||||
|
for (Player op : ai.getOpponents()) {
|
||||||
|
if (op.isCardInPlay("Tainted Remedy"))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ComputerUtil.waitForBlocking(sa) || ai.getLife() + 1 >= source.getNetToughness()
|
||||||
|
|| (ai.getLife() > 5 && !ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("Tree of Perdition".equals(sourceName)) {
|
||||||
|
boolean shouldDo = false;
|
||||||
|
|
||||||
|
if (ComputerUtil.waitForBlocking(sa))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (Player op : ai.getOpponents()) {
|
||||||
|
// if oppoent can't be targeted, or it can't lose life, try another one
|
||||||
|
if (!op.canBeTargetedBy(sa) || !op.canLoseLife())
|
||||||
|
continue;
|
||||||
|
// an opponent has more live than this toughness
|
||||||
|
if (op.getLife() + 1 >= source.getNetToughness()) {
|
||||||
|
shouldDo = true;
|
||||||
|
} else {
|
||||||
|
// opponent can't gain life, so "Tainted Remedy" should not work.
|
||||||
|
if (!op.canGainLife()) {
|
||||||
|
continue;
|
||||||
|
} else if (ai.isCardInPlay("Tainted Remedy")) { // or AI has Tainted Remedy
|
||||||
|
shouldDo = true;
|
||||||
|
} else {
|
||||||
|
for (Player ally : ai.getAllies()) {
|
||||||
|
// if an Ally has Tainted Remedy and opponent is also opponent of ally
|
||||||
|
if (ally.isCardInPlay("Tainted Remedy") && op.isOpponentOf(ally))
|
||||||
|
shouldDo = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldDo) {
|
||||||
|
sa.getTargets().add(op);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return shouldDo;
|
||||||
|
}
|
||||||
|
else if ("Evra, Halcyon Witness".equals(sourceName)) {
|
||||||
|
if (!ai.canGainLife())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int aiLife = ai.getLife();
|
||||||
|
|
||||||
|
if (source.getNetPower() > aiLife) {
|
||||||
|
if (ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the top of stack
|
||||||
|
MagicStack stack = ai.getGame().getStack();
|
||||||
|
if (!stack.isEmpty()) {
|
||||||
|
SpellAbility saTop = stack.peekAbility();
|
||||||
|
if (ComputerUtil.predictDamageFromSpell(saTop, ai) >= aiLife) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* exchangeLifeDoTriggerAINoCost.
|
||||||
|
* </p>
|
||||||
|
* @param sa
|
||||||
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
|
* @param mandatory
|
||||||
|
* a boolean.
|
||||||
|
* @param af
|
||||||
|
* a {@link forge.game.ability.AbilityFactory} object.
|
||||||
|
*
|
||||||
|
* @return a boolean.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
||||||
|
final boolean mandatory) {
|
||||||
|
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||||
|
if (tgt != null) {
|
||||||
|
sa.resetTargets();
|
||||||
|
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||||
|
sa.getTargets().add(opp);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,293 +1,313 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import forge.ai.*;
|
import com.google.common.collect.Iterables;
|
||||||
import forge.game.Game;
|
import forge.ai.*;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.Game;
|
||||||
import forge.game.card.Card;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.card.Card;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.cost.CostRemoveCounter;
|
||||||
import forge.game.player.Player;
|
import forge.game.cost.CostSacrifice;
|
||||||
import forge.game.player.PlayerCollection;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.player.PlayerPredicates;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.player.PlayerCollection;
|
||||||
import forge.util.MyRandom;
|
import forge.game.player.PlayerPredicates;
|
||||||
|
import forge.game.spellability.AbilitySub;
|
||||||
public class LifeGainAi extends SpellAbilityAi {
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.util.MyRandom;
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
public class LifeGainAi extends SpellAbilityAi {
|
||||||
*
|
|
||||||
* @see forge.ai.SpellAbilityAi#willPayCosts(forge.game.player.Player,
|
/*
|
||||||
* forge.game.spellability.SpellAbility, forge.game.cost.Cost,
|
* (non-Javadoc)
|
||||||
* forge.game.card.Card)
|
*
|
||||||
*/
|
* @see forge.ai.SpellAbilityAi#willPayCosts(forge.game.player.Player,
|
||||||
@Override
|
* forge.game.spellability.SpellAbility, forge.game.cost.Cost,
|
||||||
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
|
* forge.game.card.Card)
|
||||||
final Game game = source.getGame();
|
*/
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
@Override
|
||||||
final int life = ai.getLife();
|
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
|
||||||
|
final Game game = source.getGame();
|
||||||
boolean lifeCritical = life <= 5;
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
lifeCritical |= ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE)
|
final int life = ai.getLife();
|
||||||
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
|
|
||||||
|
boolean lifeCritical = life <= 5;
|
||||||
if (!lifeCritical) {
|
lifeCritical |= ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE)
|
||||||
// return super.willPayCosts(ai, sa, cost, source);
|
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, false)) {
|
|
||||||
return false;
|
if (!lifeCritical) {
|
||||||
}
|
// return super.willPayCosts(ai, sa, cost, source);
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
|
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa,false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
|
||||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
} else {
|
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
|
||||||
// don't sac possible blockers
|
return false;
|
||||||
if (!ph.getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
}
|
||||||
|| !game.getCombat().getDefenders().contains(ai)) {
|
} else {
|
||||||
boolean skipCheck = false;
|
// don't sac possible blockers
|
||||||
// if it's a sac self cost and the effect source is not a
|
if (!ph.getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
// creature, skip this check
|
|| !game.getCombat().getDefenders().contains(ai)) {
|
||||||
// (e.g. Woodweaver's Puzzleknot)
|
boolean skipCheck = false;
|
||||||
skipCheck |= ComputerUtilCost.isSacrificeSelfCost(cost) && !source.isCreature();
|
// if it's a sac self cost and the effect source is not a
|
||||||
|
// creature, skip this check
|
||||||
if (!skipCheck) {
|
// (e.g. Woodweaver's Puzzleknot)
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, false)) {
|
skipCheck |= ComputerUtilCost.isSacrificeSelfCost(cost) && !source.isCreature();
|
||||||
return false;
|
|
||||||
}
|
if (!skipCheck) {
|
||||||
}
|
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa,false)) {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
return true;
|
||||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
}
|
||||||
final Game game = ai.getGame();
|
|
||||||
final int life = ai.getLife();
|
@Override
|
||||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||||
|
final Game game = ai.getGame();
|
||||||
boolean lifeCritical = life <= 5;
|
final int life = ai.getLife();
|
||||||
lifeCritical |= ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE)
|
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||||
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
|
|
||||||
|
boolean lifeCritical = life <= 5;
|
||||||
// Don't use lifegain before main 2 if possible
|
lifeCritical |= ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE)
|
||||||
if (!lifeCritical && ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
|
||||||
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
|
||||||
return false;
|
// When life is critical but there is no immediate danger, try to wait until declare blockers
|
||||||
}
|
// before using the lifegain ability if it's an ability on a creature with a detrimental activation cost
|
||||||
|
if (lifeCritical
|
||||||
if (!lifeCritical && !activateForCost
|
&& sa.isAbility()
|
||||||
&& (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
&& sa.getHostCard() != null && sa.getHostCard().isCreature()
|
||||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)) {
|
&& sa.getPayCosts() != null
|
||||||
return false;
|
&& (sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class) || sa.getPayCosts().hasSpecificCostType(CostSacrifice.class))) {
|
||||||
}
|
if (!game.getStack().isEmpty()) {
|
||||||
|
SpellAbility saTop = game.getStack().peekAbility();
|
||||||
return true;
|
if (saTop.getTargets() != null && Iterables.contains(saTop.getTargets().getTargetPlayers(), ai)) {
|
||||||
}
|
return ComputerUtil.predictDamageFromSpell(saTop, ai) > 0;
|
||||||
|
}
|
||||||
/*
|
}
|
||||||
* (non-Javadoc)
|
if (game.getCombat() == null) { return false; }
|
||||||
*
|
if (!ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { return false; }
|
||||||
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
}
|
||||||
* forge.game.spellability.SpellAbility)
|
|
||||||
*/
|
// Don't use lifegain before main 2 if possible
|
||||||
@Override
|
if (!lifeCritical && ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||||
final Card source = sa.getHostCard();
|
return false;
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
}
|
||||||
|
|
||||||
final int life = ai.getLife();
|
if (!lifeCritical && !activateForCost
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
&& (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||||
int lifeAmount = 0;
|
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
return false;
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
}
|
||||||
// Set PayX here to maximum value.
|
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
return true;
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
}
|
||||||
lifeAmount = xPay;
|
|
||||||
} else {
|
/*
|
||||||
lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
* (non-Javadoc)
|
||||||
}
|
*
|
||||||
|
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
|
||||||
// Ugin AI: always use ultimate
|
* forge.game.spellability.SpellAbility)
|
||||||
if (sourceName.equals("Ugin, the Spirit Dragon")) {
|
*/
|
||||||
// TODO: somehow link with DamageDealAi for cases where +1 = win
|
@Override
|
||||||
return true;
|
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
}
|
final Card source = sa.getHostCard();
|
||||||
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
// don't use it if no life to gain
|
|
||||||
if (!activateForCost && lifeAmount <= 0) {
|
final int life = ai.getLife();
|
||||||
return false;
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
}
|
int lifeAmount = 0;
|
||||||
// don't play if the conditions aren't met, unless it would trigger a
|
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||||
// beneficial sub-condition
|
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
if (!activateForCost && !sa.getConditions().areMet(sa)) {
|
// Set PayX here to maximum value.
|
||||||
final AbilitySub abSub = sa.getSubAbility();
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
|
source.setSVar("PayX", Integer.toString(xPay));
|
||||||
if (!abSub.getConditions().areMet(abSub)) {
|
lifeAmount = xPay;
|
||||||
return false;
|
} else {
|
||||||
}
|
lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
} else {
|
}
|
||||||
return false;
|
|
||||||
}
|
// Ugin AI: always use ultimate
|
||||||
}
|
if (sourceName.equals("Ugin, the Spirit Dragon")) {
|
||||||
|
// TODO: somehow link with DamageDealAi for cases where +1 = win
|
||||||
if (!activateForCost && !ai.canGainLife()) {
|
return true;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
// don't use it if no life to gain
|
||||||
// prevent run-away activations - first time will always return true
|
if (!activateForCost && lifeAmount <= 0) {
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
// don't play if the conditions aren't met, unless it would trigger a
|
||||||
|
// beneficial sub-condition
|
||||||
if (sa.usesTargeting()) {
|
if (!activateForCost && !sa.getConditions().areMet(sa)) {
|
||||||
if (!target(ai, sa, true)) {
|
final AbilitySub abSub = sa.getSubAbility();
|
||||||
return false;
|
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
|
||||||
}
|
if (!abSub.getConditions().areMet(abSub)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
} else {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (SpellAbilityAi.isSorcerySpeed(sa)
|
|
||||||
|| sa.getSubAbility() != null || SpellAbilityAi.playReusable(ai, sa)) {
|
if (!activateForCost && !ai.canGainLife()) {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save instant-speed life-gain unless it is really worth it
|
// prevent run-away activations - first time will always return true
|
||||||
final float value = 0.9f * lifeAmount / life;
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
if (value < 0.2f) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
return MyRandom.getRandom().nextFloat() < value;
|
if (sa.usesTargeting()) {
|
||||||
}
|
if (!target(ai, sa, true)) {
|
||||||
|
return false;
|
||||||
/**
|
}
|
||||||
* <p>
|
}
|
||||||
* gainLifeDoTriggerAINoCost.
|
|
||||||
* </p>
|
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||||
* @param sa
|
return true;
|
||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
}
|
||||||
* @param mandatory
|
|
||||||
* a boolean.
|
if (SpellAbilityAi.isSorcerySpeed(sa)
|
||||||
*
|
|| sa.getSubAbility() != null || SpellAbilityAi.playReusable(ai, sa)) {
|
||||||
* @return a boolean.
|
return true;
|
||||||
*/
|
}
|
||||||
@Override
|
|
||||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
// Save instant-speed life-gain unless it is really worth it
|
||||||
final boolean mandatory) {
|
final float value = 0.9f * lifeAmount / life;
|
||||||
|
if (value < 0.2f) {
|
||||||
// If the Target is gaining life, target self.
|
return false;
|
||||||
// if the Target is modifying how much life is gained, this needs to be
|
}
|
||||||
// handled better
|
return MyRandom.getRandom().nextFloat() < value;
|
||||||
if (sa.usesTargeting()) {
|
}
|
||||||
if (!target(ai, sa, mandatory)) {
|
|
||||||
return false;
|
/**
|
||||||
}
|
* <p>
|
||||||
}
|
* gainLifeDoTriggerAINoCost.
|
||||||
|
* </p>
|
||||||
final Card source = sa.getHostCard();
|
* @param sa
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
* @param mandatory
|
||||||
// Set PayX here to maximum value.
|
* a boolean.
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
*
|
||||||
source.setSVar("PayX", Integer.toString(xPay));
|
* @return a boolean.
|
||||||
}
|
*/
|
||||||
|
@Override
|
||||||
return true;
|
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
||||||
}
|
final boolean mandatory) {
|
||||||
|
|
||||||
@Override
|
// If the Target is gaining life, target self.
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
// if the Target is modifying how much life is gained, this needs to be
|
||||||
return doTriggerAINoCost(ai, sa, true);
|
// handled better
|
||||||
}
|
if (sa.usesTargeting()) {
|
||||||
|
if (!target(ai, sa, mandatory)) {
|
||||||
private boolean target(Player ai, SpellAbility sa, boolean mandatory) {
|
return false;
|
||||||
Card source = sa.getHostCard();
|
}
|
||||||
sa.resetTargets();
|
}
|
||||||
// TODO : add add even more logic into it
|
|
||||||
// try to target opponents first if that would kill them
|
final Card source = sa.getHostCard();
|
||||||
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
PlayerCollection opps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
PlayerCollection allies = ai.getAllies().filter(PlayerPredicates.isTargetableBy(sa));
|
// Set PayX here to maximum value.
|
||||||
|
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
if (sa.canTarget(ai) && ComputerUtil.lifegainPositive(ai, source)) {
|
source.setSVar("PayX", Integer.toString(xPay));
|
||||||
sa.getTargets().add(ai);
|
}
|
||||||
} else {
|
|
||||||
boolean hasTgt = false;
|
return true;
|
||||||
// check for Lifegain negative on opponents
|
}
|
||||||
for (Player opp : opps) {
|
|
||||||
if (ComputerUtil.lifegainNegative(opp, source)) {
|
@Override
|
||||||
sa.getTargets().add(opp);
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
hasTgt = true;
|
return doTriggerAINoCost(ai, sa, true);
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
}
|
private boolean target(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (!hasTgt) {
|
Card source = sa.getHostCard();
|
||||||
// lifegain on ally
|
sa.resetTargets();
|
||||||
for (Player ally : allies) {
|
// TODO : add add even more logic into it
|
||||||
if (ComputerUtil.lifegainPositive(ally, source)) {
|
// try to target opponents first if that would kill them
|
||||||
sa.getTargets().add(ally);
|
|
||||||
hasTgt = true;
|
PlayerCollection opps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
break;
|
PlayerCollection allies = ai.getAllies().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
}
|
|
||||||
}
|
if (sa.canTarget(ai) && ComputerUtil.lifegainPositive(ai, source)) {
|
||||||
}
|
sa.getTargets().add(ai);
|
||||||
if (!hasTgt && mandatory) {
|
} else {
|
||||||
// need to target something but its neither negative against
|
boolean hasTgt = false;
|
||||||
// opponents,
|
// check for Lifegain negative on opponents
|
||||||
// nor posive against allies
|
for (Player opp : opps) {
|
||||||
|
if (ComputerUtil.lifegainNegative(opp, source)) {
|
||||||
// hurting ally is probably better than healing opponent
|
sa.getTargets().add(opp);
|
||||||
// look for Lifegain not Negative (case of lifegain negated)
|
hasTgt = true;
|
||||||
for (Player ally : allies) {
|
break;
|
||||||
if (!ComputerUtil.lifegainNegative(ally, source)) {
|
}
|
||||||
sa.getTargets().add(ally);
|
}
|
||||||
hasTgt = true;
|
if (!hasTgt) {
|
||||||
break;
|
// lifegain on ally
|
||||||
}
|
for (Player ally : allies) {
|
||||||
}
|
if (ComputerUtil.lifegainPositive(ally, source)) {
|
||||||
if (!hasTgt) {
|
sa.getTargets().add(ally);
|
||||||
// same for opponent lifegain not positive
|
hasTgt = true;
|
||||||
for (Player opp : opps) {
|
break;
|
||||||
if (!ComputerUtil.lifegainPositive(opp, source)) {
|
}
|
||||||
sa.getTargets().add(opp);
|
}
|
||||||
hasTgt = true;
|
}
|
||||||
break;
|
if (!hasTgt && mandatory) {
|
||||||
}
|
// need to target something but its neither negative against
|
||||||
}
|
// opponents,
|
||||||
}
|
// nor posive against allies
|
||||||
|
|
||||||
// still no luck, try to target ally with most life
|
// hurting ally is probably better than healing opponent
|
||||||
if (!allies.isEmpty()) {
|
// look for Lifegain not Negative (case of lifegain negated)
|
||||||
Player ally = allies.max(PlayerPredicates.compareByLife());
|
for (Player ally : allies) {
|
||||||
sa.getTargets().add(ally);
|
if (!ComputerUtil.lifegainNegative(ally, source)) {
|
||||||
hasTgt = true;
|
sa.getTargets().add(ally);
|
||||||
}
|
hasTgt = true;
|
||||||
// better heal opponent which most life then the one with the
|
break;
|
||||||
// lowest
|
}
|
||||||
if (!hasTgt) {
|
}
|
||||||
Player opp = opps.max(PlayerPredicates.compareByLife());
|
if (!hasTgt) {
|
||||||
sa.getTargets().add(opp);
|
// same for opponent lifegain not positive
|
||||||
hasTgt = true;
|
for (Player opp : opps) {
|
||||||
}
|
if (!ComputerUtil.lifegainPositive(opp, source)) {
|
||||||
}
|
sa.getTargets().add(opp);
|
||||||
if (!hasTgt) {
|
hasTgt = true;
|
||||||
return false;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
}
|
// still no luck, try to target ally with most life
|
||||||
|
if (!allies.isEmpty()) {
|
||||||
|
Player ally = allies.max(PlayerPredicates.compareByLife());
|
||||||
|
sa.getTargets().add(ally);
|
||||||
|
hasTgt = true;
|
||||||
|
}
|
||||||
|
// better heal opponent which most life then the one with the
|
||||||
|
// lowest
|
||||||
|
if (!hasTgt) {
|
||||||
|
Player opp = opps.max(PlayerPredicates.compareByLife());
|
||||||
|
sa.getTargets().add(opp);
|
||||||
|
hasTgt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasTgt) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user