mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 10:48:00 +00:00
Compare commits
1940 Commits
untapRewor
...
forge-1.6.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4049d23562 | ||
|
|
50e2cf1f88 | ||
|
|
7f5a56fe4f | ||
|
|
bf74bd2f09 | ||
|
|
8f20aa39ca | ||
|
|
922c1fb541 | ||
|
|
55973e8c75 | ||
|
|
54e8c6f0ec | ||
|
|
0837e0de8c | ||
|
|
da6188e61c | ||
|
|
52fef4b724 | ||
|
|
f29cd22926 | ||
|
|
ab6a322f98 | ||
|
|
e44125ec1c | ||
|
|
558fbd3946 | ||
|
|
1051fc676b | ||
|
|
23737aa21a | ||
|
|
47f527b3ff | ||
|
|
b06add33d2 | ||
|
|
24d2c8dc2f | ||
|
|
2fb6849ae2 | ||
|
|
f5de946830 | ||
|
|
39deffdc56 | ||
|
|
85062cba83 | ||
|
|
17392a29ab | ||
|
|
aa21883049 | ||
|
|
ca7691adee | ||
|
|
51fdf21d1e | ||
|
|
015bb428e4 | ||
|
|
645b6d89d2 | ||
|
|
e60986e04e | ||
|
|
e8eb7abf48 | ||
|
|
425472cd54 | ||
|
|
48f109b7f6 | ||
|
|
df09cba493 | ||
|
|
420db21d83 | ||
|
|
54436ac2e3 | ||
|
|
bb0d6c4606 | ||
|
|
dc058fccc1 | ||
|
|
8af5c1302d | ||
|
|
29a7c98185 | ||
|
|
bb4f4272ca | ||
|
|
01183c454b | ||
|
|
e3863b0712 | ||
|
|
0cff9ebdf0 | ||
|
|
5fc1f92309 | ||
|
|
7b0247aba5 | ||
|
|
f9f2b65e45 | ||
|
|
e4fb9d8a9b | ||
|
|
f8dab4a4b2 | ||
|
|
088062a93e | ||
|
|
6049a681e8 | ||
|
|
c622291c4e | ||
|
|
450c7c5b71 | ||
|
|
b5ebf2185f | ||
|
|
fa240f6dc2 | ||
|
|
b23b4a0888 | ||
|
|
2fc2d623a8 | ||
|
|
58c7d69d27 | ||
|
|
730a7adf2a | ||
|
|
39d24724c6 | ||
|
|
1053b3fdea | ||
|
|
cbc3f6a568 | ||
|
|
d007e86779 | ||
|
|
0503177c77 | ||
|
|
ffc2187fea | ||
|
|
69d8aeb875 | ||
|
|
2e15415666 | ||
|
|
17333c2ae9 | ||
|
|
b9064dad3f | ||
|
|
b1c87c7f83 | ||
|
|
e8dba06444 | ||
|
|
46283cebd3 | ||
|
|
86573dedf1 | ||
|
|
f57ece280f | ||
|
|
f4f0950f72 | ||
|
|
d507de72ff | ||
|
|
9b1c8e0980 | ||
|
|
1e46df363f | ||
|
|
eface99d95 | ||
|
|
9dc42e68fb | ||
|
|
1a6b51bae2 | ||
|
|
39c6de95fd | ||
|
|
d3275d78fb | ||
|
|
29afbd67a9 | ||
|
|
d4aefdeca1 | ||
|
|
0df56097af | ||
|
|
3bbadca04f | ||
|
|
9034b28747 | ||
|
|
332116509d | ||
|
|
37c846330f | ||
|
|
93dfb28b70 | ||
|
|
20e41c4fda | ||
|
|
5d61cd795a | ||
|
|
4513976f20 | ||
|
|
ba0b5f813b | ||
|
|
1e3531efc6 | ||
|
|
0e9287ba2f | ||
|
|
f65ec5455f | ||
|
|
26e0bdb545 | ||
|
|
dc300a9929 | ||
|
|
d171577127 | ||
|
|
c9e0d7722c | ||
|
|
a95321de4e | ||
|
|
2ec4479994 | ||
|
|
414b8b0bae | ||
|
|
b4686e5643 | ||
|
|
28f6edaccb | ||
|
|
2359a4b435 | ||
|
|
5b0bdff50c | ||
|
|
a7c5dd2681 | ||
|
|
4d9bbef480 | ||
|
|
20b599d52a | ||
|
|
e9bcdcc133 | ||
|
|
90dc1a6898 | ||
|
|
3a56cd60b8 | ||
|
|
51ec7b049b | ||
|
|
1061b2e605 | ||
|
|
a71f8e2932 | ||
|
|
90e9f738b0 | ||
|
|
68a33b1322 | ||
|
|
d74f4aa671 | ||
|
|
498acbd496 | ||
|
|
56cb550850 | ||
|
|
f28b9c8d03 | ||
|
|
9a00c7d02a | ||
|
|
a7c69d6841 | ||
|
|
63f9457162 | ||
|
|
0a18cff766 | ||
|
|
95e5316336 | ||
|
|
9888263507 | ||
|
|
2001d6e685 | ||
|
|
39899fe859 | ||
|
|
1ef8d32dfe | ||
|
|
9b16919c37 | ||
|
|
a4f70cea78 | ||
|
|
103669eee3 | ||
|
|
25260f8d7c | ||
|
|
eeec573798 | ||
|
|
f6e56ee23f | ||
|
|
d175ca87c7 | ||
|
|
a59412550d | ||
|
|
dd78ab67eb | ||
|
|
05100f5689 | ||
|
|
69b30e4313 | ||
|
|
d509c87371 | ||
|
|
aa15b7d5da | ||
|
|
674514c158 | ||
|
|
961fdae04a | ||
|
|
73ac827d0b | ||
|
|
6961d63127 | ||
|
|
19f36789c2 | ||
|
|
31dea9a94d | ||
|
|
c6839717f9 | ||
|
|
5a0f5bc4ba | ||
|
|
ead2f62b5f | ||
|
|
779db06765 | ||
|
|
288fe96a94 | ||
|
|
f3b86e0532 | ||
|
|
deeccf93d4 | ||
|
|
c03848dc1d | ||
|
|
a44b2a31cd | ||
|
|
9370dc0036 | ||
|
|
4ef89b9963 | ||
|
|
30dcdfb88a | ||
|
|
833395fb92 | ||
|
|
52b5271af5 | ||
|
|
b8a7696f40 | ||
|
|
3cb80c43bf | ||
|
|
225247d9dc | ||
|
|
2db862ce2d | ||
|
|
e0a1990014 | ||
|
|
68a559552d | ||
|
|
2ddcb34a08 | ||
|
|
28f794743c | ||
|
|
5b830aeb37 | ||
|
|
499f33ed69 | ||
|
|
46ead353d0 | ||
|
|
36c04efbff | ||
|
|
653d214027 | ||
|
|
f60fdf3fa5 | ||
|
|
b3015128a1 | ||
|
|
2c038396b6 | ||
|
|
2370c2c8ba | ||
|
|
d743bd5168 | ||
|
|
4880448102 | ||
|
|
b34fd44cfd | ||
|
|
4acfd71123 | ||
|
|
dd72be2f7a | ||
|
|
b140ca57be | ||
|
|
14f2b25c45 | ||
|
|
8bfcd1815a | ||
|
|
95a8f5c031 | ||
|
|
1b40987bbd | ||
|
|
febeaf4eec | ||
|
|
1079ad8e83 | ||
|
|
d67a1e7534 | ||
|
|
46b3c622f0 | ||
|
|
8714399b51 | ||
|
|
19c07f5bef | ||
|
|
33e7cde2d6 | ||
|
|
12d6790495 | ||
|
|
37f2e75dc4 | ||
|
|
019969d804 | ||
|
|
8564d30efc | ||
|
|
51c15a5a4c | ||
|
|
3a475f44e4 | ||
|
|
47f2123286 | ||
|
|
65ef844444 | ||
|
|
57b4135f95 | ||
|
|
3d32915d9f | ||
|
|
379f9699e8 | ||
|
|
0e831f88b9 | ||
|
|
8e0c1f4fe3 | ||
|
|
d7293c7830 | ||
|
|
817b8bf15b | ||
|
|
26694ffa5c | ||
|
|
6fde7149a4 | ||
|
|
815b2bc4aa | ||
|
|
5c7a0adf33 | ||
|
|
3a2c66df0b | ||
|
|
b249560dbb | ||
|
|
a165f314d3 | ||
|
|
0c27a41566 | ||
|
|
955a16f1e0 | ||
|
|
1ced1058c9 | ||
|
|
ea26f6c689 | ||
|
|
fc706c8035 | ||
|
|
74589d59e4 | ||
|
|
70046c5261 | ||
|
|
3165c57b0e | ||
|
|
27968cd561 | ||
|
|
9cb4911e29 | ||
|
|
933fe7ad49 | ||
|
|
b13503c8a7 | ||
|
|
2bdd598565 | ||
|
|
81bcacefc0 | ||
|
|
215cd20b90 | ||
|
|
0701fd6bbc | ||
|
|
3e89f64944 | ||
|
|
3041788be1 | ||
|
|
f5e5ddbbb9 | ||
|
|
c0578fb246 | ||
|
|
0489a2ba83 | ||
|
|
25109de700 | ||
|
|
d86aa31ab8 | ||
|
|
0846017766 | ||
|
|
a964f5b71b | ||
|
|
d311231c96 | ||
|
|
b2018aafe6 | ||
|
|
7ab4d13507 | ||
|
|
7a6409c11c | ||
|
|
25fb9857d8 | ||
|
|
b7b2c88916 | ||
|
|
2b714672a2 | ||
|
|
0ed4670cfb | ||
|
|
fa610b4bfd | ||
|
|
2af1895e26 | ||
|
|
b919ccb50c | ||
|
|
87ed2140b4 | ||
|
|
b9d5326c30 | ||
|
|
0af1085f79 | ||
|
|
c793065cae | ||
|
|
248a2447d2 | ||
|
|
df2932bec5 | ||
|
|
b7fb854389 | ||
|
|
7c322e1e33 | ||
|
|
74b3ecd8ce | ||
|
|
82060740c6 | ||
|
|
45b1b3f38b | ||
|
|
f72526fa08 | ||
|
|
e96fa0cc96 | ||
|
|
7966684424 | ||
|
|
56b5a6d3c2 | ||
|
|
0516693eec | ||
|
|
8c40ba9ef3 | ||
|
|
b12f97daa2 | ||
|
|
815a0f5da9 | ||
|
|
2e319a4c9b | ||
|
|
7b151bf78a | ||
|
|
f091c0592e | ||
|
|
76ade204bb | ||
|
|
046f2a58d9 | ||
|
|
a0e2de0aec | ||
|
|
fffb01a81c | ||
|
|
022875f07a | ||
|
|
e60daf0536 | ||
|
|
b17c4a188c | ||
|
|
da32b24fa8 | ||
|
|
d25825c595 | ||
|
|
15eca3f80a | ||
|
|
3f428ee7d4 | ||
|
|
df9b462353 | ||
|
|
77c15b1673 | ||
|
|
c4a1f22584 | ||
|
|
369a366b4f | ||
|
|
bcb626872f | ||
|
|
deb0d7e2f1 | ||
|
|
75ab38836e | ||
|
|
68cece62c3 | ||
|
|
51e945df72 | ||
|
|
4eb21594b4 | ||
|
|
6362390e6a | ||
|
|
e7f0b33dd2 | ||
|
|
b0300edc9f | ||
|
|
d8a5e573da | ||
|
|
bb84b4a4d4 | ||
|
|
b66eb6266d | ||
|
|
e0b61e7453 | ||
|
|
7018beab62 | ||
|
|
09fba334d4 | ||
|
|
f6d6e45fed | ||
|
|
7d15b021aa | ||
|
|
83774d57df | ||
|
|
414665166c | ||
|
|
ab6d856880 | ||
|
|
538430009b | ||
|
|
1db29e8846 | ||
|
|
6ad5e73089 | ||
|
|
e4c0a3e6b3 | ||
|
|
311c8138d6 | ||
|
|
447453977d | ||
|
|
085c9f0042 | ||
|
|
ed4be69c93 | ||
|
|
97bcf8f9c7 | ||
|
|
2fd4f9c31f | ||
|
|
d9ee072064 | ||
|
|
d1199e2698 | ||
|
|
d5b1799cba | ||
|
|
a1d241dd6e | ||
|
|
82d11283f7 | ||
|
|
35dd052c93 | ||
|
|
09d2e1fb30 | ||
|
|
cc10e91431 | ||
|
|
8ab0857750 | ||
|
|
21989c31a5 | ||
|
|
f651cd42f4 | ||
|
|
b628b8ca00 | ||
|
|
3534f1ec05 | ||
|
|
98d80a1328 | ||
|
|
2d3a538f6d | ||
|
|
573902f51e | ||
|
|
47fd6734ab | ||
|
|
4047dfe727 | ||
|
|
64085463d8 | ||
|
|
18154ca0b7 | ||
|
|
0fcbc62801 | ||
|
|
84455ba61d | ||
|
|
7c27efbd64 | ||
|
|
6246b16372 | ||
|
|
f497f75c3d | ||
|
|
4a957c240a | ||
|
|
7910a37902 | ||
|
|
3b6160cd88 | ||
|
|
ae545ed169 | ||
|
|
4406c7ba72 | ||
|
|
b964cc6448 | ||
|
|
ca4687f79a | ||
|
|
42daf6b5ee | ||
|
|
f696a5488c | ||
|
|
853aad6e65 | ||
|
|
3fe1aa45a2 | ||
|
|
d8ebc3b5f7 | ||
|
|
23f35854ba | ||
|
|
5cecbe63b1 | ||
|
|
ae6e290a33 | ||
|
|
eb0a30f013 | ||
|
|
be1574b44d | ||
|
|
c5aa8cfae7 | ||
|
|
2e3e3537fb | ||
|
|
39378e9776 | ||
|
|
6a73a990db | ||
|
|
be73f20ce0 | ||
|
|
44e411f925 | ||
|
|
c7f0def063 | ||
|
|
986a273772 | ||
|
|
6a7bfd48bf | ||
|
|
4e6addcf84 | ||
|
|
5a3db18220 | ||
|
|
2df5373902 | ||
|
|
3a53ab8a9c | ||
|
|
1ea93a6f2e | ||
|
|
970c3451e9 | ||
|
|
0960ed98f0 | ||
|
|
4701371b17 | ||
|
|
11c5b00130 | ||
|
|
e0f2d09dfd | ||
|
|
2d355d4ea3 | ||
|
|
bea46a7a8c | ||
|
|
d13439500f | ||
|
|
bd54bbe026 | ||
|
|
15f147ac9c | ||
|
|
c2248ef905 | ||
|
|
74c92590fc | ||
|
|
552a7130a5 | ||
|
|
8d1c02012e | ||
|
|
c4963821e5 | ||
|
|
8a648c9ccf | ||
|
|
a2b5bb911c | ||
|
|
e7cbee211d | ||
|
|
a553103e39 | ||
|
|
45a5e76e63 | ||
|
|
8122589842 | ||
|
|
d754c2c4d3 | ||
|
|
7e9843aae5 | ||
|
|
2897d4304d | ||
|
|
f5400d18bd | ||
|
|
46983de66f | ||
|
|
049e129606 | ||
|
|
b5deb0b252 | ||
|
|
0bb42a2794 | ||
|
|
a9b5e1d112 | ||
|
|
c1c9271458 | ||
|
|
44e4dfecca | ||
|
|
d809fa6b91 | ||
|
|
c5a89747b8 | ||
|
|
36341a85cd | ||
|
|
253256799c | ||
|
|
16fa79132b | ||
|
|
f02ab6c073 | ||
|
|
ef208f3442 | ||
|
|
bdcaf785bd | ||
|
|
318c4891a4 | ||
|
|
a6fcec519d | ||
|
|
7f3bc8f7f9 | ||
|
|
f2e7068706 | ||
|
|
67c0722db8 | ||
|
|
60ad4cf0d1 | ||
|
|
d76004509d | ||
|
|
bac8497bc3 | ||
|
|
ac9636c44e | ||
|
|
89b534a580 | ||
|
|
daa982389e | ||
|
|
e56ee38933 | ||
|
|
b12ed3307a | ||
|
|
08446a344c | ||
|
|
5ec6589175 | ||
|
|
bdff4a12e7 | ||
|
|
f610203a53 | ||
|
|
37b7408163 | ||
|
|
67684fa4ec | ||
|
|
5787bf7660 | ||
|
|
e621972ad2 | ||
|
|
84146df00e | ||
|
|
a27673ef5b | ||
|
|
4c8072afd2 | ||
|
|
6340f92c95 | ||
|
|
9349477f21 | ||
|
|
96e243e4c9 | ||
|
|
828885c9b0 | ||
|
|
4694ea8014 | ||
|
|
41d0fa9227 | ||
|
|
7e158620f3 | ||
|
|
4ffbaf87a0 | ||
|
|
0a32dcffa6 | ||
|
|
f20f4dcfe4 | ||
|
|
377e2dc9a6 | ||
|
|
a25404d029 | ||
|
|
838c4daf25 | ||
|
|
ef8533237e | ||
|
|
30cdf13d88 | ||
|
|
e739894759 | ||
|
|
dfd07d1d12 | ||
|
|
3ae3ec003e | ||
|
|
f68ac2e43a | ||
|
|
8acb143fea | ||
|
|
3cdbf3eb67 | ||
|
|
1f7faa773c | ||
|
|
f27748eeca | ||
|
|
789019fff1 | ||
|
|
88d768f8b7 | ||
|
|
e2f7faa4d7 | ||
|
|
344792d91c | ||
|
|
879322d3b0 | ||
|
|
e9a93a0f8d | ||
|
|
a9482c349e | ||
|
|
2c8bc0b582 | ||
|
|
62ba27d09f | ||
|
|
d6efcee188 | ||
|
|
4744f11e69 | ||
|
|
20549fe0ca | ||
|
|
8c023fbfea | ||
|
|
158711f7a9 | ||
|
|
5be2ab9358 | ||
|
|
99f07c1471 | ||
|
|
f7b1a24fdd | ||
|
|
610e0e546a | ||
|
|
eb02f4acec | ||
|
|
ef95f02fa2 | ||
|
|
6b38bc4b7f | ||
|
|
57e788a69d | ||
|
|
e2c28897d1 | ||
|
|
34cb7ff9de | ||
|
|
07123286aa | ||
|
|
017f9f1984 | ||
|
|
06b9143d57 | ||
|
|
b1e9f4a110 | ||
|
|
d24fa32175 | ||
|
|
1f7dade4f9 | ||
|
|
58e99ca69b | ||
|
|
ca43cf8934 | ||
|
|
56d14d25eb | ||
|
|
bf548096f1 | ||
|
|
e53e9a5bdf | ||
|
|
64f1ad9778 | ||
|
|
c3621cc8e6 | ||
|
|
250f4484ce | ||
|
|
4d65ab429d | ||
|
|
2cfb4917b1 | ||
|
|
4bcb516abd | ||
|
|
29c4d6d4d1 | ||
|
|
a7b3d18f66 | ||
|
|
32256a398a | ||
|
|
14e6baf455 | ||
|
|
24e7cc7999 | ||
|
|
210a6637bb | ||
|
|
23688b281b | ||
|
|
f968e310f2 | ||
|
|
afc55d4b2e | ||
|
|
ebd0e12bee | ||
|
|
bd6a1f3a2b | ||
|
|
ca6873d893 | ||
|
|
759703bf56 | ||
|
|
d52d850c85 | ||
|
|
475ae289fd | ||
|
|
ac6b983cb5 | ||
|
|
1f4f7fc7f4 | ||
|
|
e85ed216be | ||
|
|
427e34607d | ||
|
|
2778318b10 | ||
|
|
102119679c | ||
|
|
5467701fa0 | ||
|
|
d6dc081e42 | ||
|
|
d87d825bce | ||
|
|
d4625864df | ||
|
|
7ca5eee57e | ||
|
|
38d708ce65 | ||
|
|
c6f1dc6bd1 | ||
|
|
aeb0bfc971 | ||
|
|
f1690f2d84 | ||
|
|
e20c79fa4b | ||
|
|
f6201d178e | ||
|
|
17b2a2b023 | ||
|
|
d17015cc88 | ||
|
|
5649ac6d7d | ||
|
|
d1cc547239 | ||
|
|
ce1aca2674 | ||
|
|
f3729d0059 | ||
|
|
d6c5d7f04d | ||
|
|
fdd009908d | ||
|
|
4be26f575c | ||
|
|
84519d10ea | ||
|
|
380a2649d0 | ||
|
|
e555c577de | ||
|
|
377c0b6a9a | ||
|
|
63f0df433f | ||
|
|
1fb56331f9 | ||
|
|
5a348f06cd | ||
|
|
d869d6b43f | ||
|
|
6e9827719b | ||
|
|
0d1105377e | ||
|
|
9b9f88129f | ||
|
|
d7d59d64b8 | ||
|
|
015e51d668 | ||
|
|
8f8fca8d80 | ||
|
|
81d8c42f29 | ||
|
|
630b9d3dd7 | ||
|
|
0db2e0d95b | ||
|
|
0be5a1180f | ||
|
|
079ad0223a | ||
|
|
bcdc3c849a | ||
|
|
cb7d879abb | ||
|
|
a0ea1f1d3f | ||
|
|
b4cc8421c5 | ||
|
|
1ac8026b7f | ||
|
|
f68e3dee71 | ||
|
|
7abbab5f42 | ||
|
|
9033bbd410 | ||
|
|
8d96dd99fa | ||
|
|
e327c74976 | ||
|
|
4319111c55 | ||
|
|
efaac25920 | ||
|
|
82ffc4e9d5 | ||
|
|
bda8c00e13 | ||
|
|
e21d2408bb | ||
|
|
aede20c610 | ||
|
|
205f3462b1 | ||
|
|
742cfea656 | ||
|
|
7af1f7a452 | ||
|
|
e649db6775 | ||
|
|
461b72376f | ||
|
|
9b03426f0e | ||
|
|
e1b14ecb51 | ||
|
|
972327bd72 | ||
|
|
db25466646 | ||
|
|
68ad5ad9c0 | ||
|
|
053fc12b35 | ||
|
|
5ec5cb5f7f | ||
|
|
ccf2abc391 | ||
|
|
d2e5eb133b | ||
|
|
5ba4ebbe38 | ||
|
|
5c1702c25b | ||
|
|
65da3d55a2 | ||
|
|
3e1fe53b77 | ||
|
|
0b9a9fc2a9 | ||
|
|
86ff6b402a | ||
|
|
c43b14f850 | ||
|
|
45b1e825e1 | ||
|
|
fa96bd5b5d | ||
|
|
6c671eaf9d | ||
|
|
190be3f7ea | ||
|
|
7642d588a4 | ||
|
|
a43b2a859b | ||
|
|
5c79cb7de6 | ||
|
|
b80d499a36 | ||
|
|
0b5c70b4bd | ||
|
|
cb4a4d81ae | ||
|
|
349640a3a0 | ||
|
|
c88399d7a0 | ||
|
|
aa86f8eae3 | ||
|
|
b6408100e5 | ||
|
|
88736fb3a7 | ||
|
|
8523288884 | ||
|
|
3d46a6e809 | ||
|
|
c82ed43c69 | ||
|
|
9edaa9561e | ||
|
|
93f1bb70b4 | ||
|
|
3bb42a95c9 | ||
|
|
6ce62b7316 | ||
|
|
e40388c244 | ||
|
|
8eab842416 | ||
|
|
4febc835e3 | ||
|
|
56dbb45d63 | ||
|
|
3b50a9ef8b | ||
|
|
ba3e8de481 | ||
|
|
a18f142add | ||
|
|
c2e4888711 | ||
|
|
b466ff38a3 | ||
|
|
96ce695aae | ||
|
|
52307c02d0 | ||
|
|
c714187eee | ||
|
|
9f1b93373d | ||
|
|
eeacdf6c80 | ||
|
|
bf60039ee7 | ||
|
|
78b2972943 | ||
|
|
3eb322222a | ||
|
|
ef95d3eab9 | ||
|
|
829e49a2ba | ||
|
|
0a768211b9 | ||
|
|
d0bdeec832 | ||
|
|
9047168a3f | ||
|
|
9a2f2368e9 | ||
|
|
5cf56e12c3 | ||
|
|
a67deeda15 | ||
|
|
0b2a1ce44a | ||
|
|
5ab7bcaf3f | ||
|
|
18bd6ec204 | ||
|
|
da984227b1 | ||
|
|
bd5cc06504 | ||
|
|
8f99034a02 | ||
|
|
d66700cbf3 | ||
|
|
bb62c0bebb | ||
|
|
13652e30ac | ||
|
|
b7d93783c0 | ||
|
|
5ba9f5f58f | ||
|
|
fa93702bc8 | ||
|
|
c9c4d5492d | ||
|
|
bcec151b1d | ||
|
|
4039c28140 | ||
|
|
a5ad3ad60a | ||
|
|
d3e83b2601 | ||
|
|
0aee987536 | ||
|
|
495257882b | ||
|
|
58f6463a5d | ||
|
|
65b7f2f4a3 | ||
|
|
ed67aafe2e | ||
|
|
71cc16953c | ||
|
|
9a1e467bb7 | ||
|
|
a4d9e1ef84 | ||
|
|
41dbc468bd | ||
|
|
8a9984f7df | ||
|
|
eca7ea2fbe | ||
|
|
05e160cebe | ||
|
|
447c3dff5a | ||
|
|
2dd97028b1 | ||
|
|
24db729e96 | ||
|
|
c10c9f441f | ||
|
|
6bf72e6828 | ||
|
|
bf7b446586 | ||
|
|
ec463b414f | ||
|
|
faf8c57907 | ||
|
|
7b90b6a801 | ||
|
|
cd8410e87a | ||
|
|
7dbf6519a0 | ||
|
|
9811eedfc5 | ||
|
|
e337107575 | ||
|
|
70b7af6f16 | ||
|
|
17584a03f1 | ||
|
|
2472db72ca | ||
|
|
e57e87d3c2 | ||
|
|
7c9849f845 | ||
|
|
1c61030aae | ||
|
|
bd8b136b2b | ||
|
|
4bb0bcfa90 | ||
|
|
084a9812a7 | ||
|
|
65947bee28 | ||
|
|
10acab8bb9 | ||
|
|
eb03c01cb0 | ||
|
|
9f8f7f132a | ||
|
|
62aec9956e | ||
|
|
9376ba3429 | ||
|
|
7f8ddb0cab | ||
|
|
dd3c93287e | ||
|
|
6da9d86b1b | ||
|
|
ad4d429aff | ||
|
|
051e607724 | ||
|
|
cc1f5abe28 | ||
|
|
27a6f9c3a4 | ||
|
|
37eeec088d | ||
|
|
1c3e8c32b9 | ||
|
|
ee1b8d1c9a | ||
|
|
7fb34b4887 | ||
|
|
b65b0e4758 | ||
|
|
4e8e77924a | ||
|
|
3e7f8c8916 | ||
|
|
5005cf9102 | ||
|
|
dafd2e6a2a | ||
|
|
4cfcbaf603 | ||
|
|
22dc1a27a0 | ||
|
|
c374e0ae81 | ||
|
|
fc1b3547b2 | ||
|
|
b0d3fbe783 | ||
|
|
3a02a10258 | ||
|
|
33d83dc0c5 | ||
|
|
d35a2cdead | ||
|
|
507c88269b | ||
|
|
2c7dfc0c41 | ||
|
|
a8ab95f321 | ||
|
|
672cff54ef | ||
|
|
44cbd9af3e | ||
|
|
d3bcfea727 | ||
|
|
20fa72d24e | ||
|
|
10faa20490 | ||
|
|
62c9d187eb | ||
|
|
a5148e8983 | ||
|
|
0bb7a84e55 | ||
|
|
1218786b3e | ||
|
|
62a1ee6d3d | ||
|
|
64f17b49fe | ||
|
|
0d939da60d | ||
|
|
cc321b1b2e | ||
|
|
71bf43b63a | ||
|
|
2d7840aa7d | ||
|
|
a1be9d0278 | ||
|
|
1a99afa99e | ||
|
|
27eda6f9b2 | ||
|
|
8a18f815b4 | ||
|
|
2b529a07c2 | ||
|
|
c0349d87fb | ||
|
|
c488ab4772 | ||
|
|
d83f8deb7a | ||
|
|
f1f480d16a | ||
|
|
a1ffa625e1 | ||
|
|
d7e38f7fd8 | ||
|
|
a5683d4f12 | ||
|
|
cb1d4b7904 | ||
|
|
c6def2cc5b | ||
|
|
81dc9fecba | ||
|
|
4629817268 | ||
|
|
b6bbe3d18b | ||
|
|
b437db8ffa | ||
|
|
3be58fee75 | ||
|
|
fe09671795 | ||
|
|
9f35da4698 | ||
|
|
3c5a62056b | ||
|
|
43a38e3c6d | ||
|
|
f5a89afa93 | ||
|
|
7a49dd28e1 | ||
|
|
d14f20bc43 | ||
|
|
14a40e493f | ||
|
|
9b8e63501a | ||
|
|
61097e9f80 | ||
|
|
404ce34076 | ||
|
|
e546b6a689 | ||
|
|
7e31e15ead | ||
|
|
62d7824155 | ||
|
|
8c5a90d6e9 | ||
|
|
9345ff6725 | ||
|
|
c45573ff2e | ||
|
|
55300347ce | ||
|
|
c85584445e | ||
|
|
ceeb5d623f | ||
|
|
1f4eff8b1c | ||
|
|
41af869d3c | ||
|
|
2747d93e4c | ||
|
|
5c9251e295 | ||
|
|
fa67ee73a5 | ||
|
|
e1659a4539 | ||
|
|
d5b578b306 | ||
|
|
32b56018a6 | ||
|
|
aa12085345 | ||
|
|
3dd20d9d0b | ||
|
|
8b922fc8f2 | ||
|
|
a527211bcf | ||
|
|
c769a8adcf | ||
|
|
c88a419e03 | ||
|
|
b9618509be | ||
|
|
1076b1e29f | ||
|
|
4717955afb | ||
|
|
af771d0c2c | ||
|
|
8fea6b4cc0 | ||
|
|
9249029dc1 | ||
|
|
e5d40554e1 | ||
|
|
8b8e39ff41 | ||
|
|
40591b04d2 | ||
|
|
1c1e2416e4 | ||
|
|
684e9327cd | ||
|
|
5906891fec | ||
|
|
e0c9d5e6e0 | ||
|
|
bdf9d3f88e | ||
|
|
960760a564 | ||
|
|
f488cb1d0b | ||
|
|
c4e2004af3 | ||
|
|
dcb151f561 | ||
|
|
4550ee26e3 | ||
|
|
ecdea545fc | ||
|
|
643c08850b | ||
|
|
fed6d788cf | ||
|
|
3ccd979b63 | ||
|
|
be52fb772e | ||
|
|
633c106708 | ||
|
|
aec62cbffb | ||
|
|
5b61712d70 | ||
|
|
7fa248db4c | ||
|
|
33f04148d3 | ||
|
|
3aa33be214 | ||
|
|
612045f8c0 | ||
|
|
0bd6b16247 | ||
|
|
eaa7d296d2 | ||
|
|
235162fb84 | ||
|
|
fc4a491111 | ||
|
|
0cd9b1435b | ||
|
|
61184ade4c | ||
|
|
f2e29c9c93 | ||
|
|
e676073548 | ||
|
|
6b315b2571 | ||
|
|
6e028a1c45 | ||
|
|
f07e2bc2ec | ||
|
|
3bc4124fe8 | ||
|
|
bb5af6298e | ||
|
|
3c6c0f534d | ||
|
|
4f3a8590dd | ||
|
|
2a993eb0e0 | ||
|
|
f99e819ff0 | ||
|
|
0f6b65a9ff | ||
|
|
6e94378cc1 | ||
|
|
ff31718839 | ||
|
|
2ab5a286c5 | ||
|
|
e07208e81b | ||
|
|
1ec9c6cade | ||
|
|
04e94f68f5 | ||
|
|
e325f42ca8 | ||
|
|
f3c078b23c | ||
|
|
4f0deea15f | ||
|
|
db396e6b5c | ||
|
|
dfe7ea0ecc | ||
|
|
55cba2513f | ||
|
|
9bbc3c8ef2 | ||
|
|
c473ecb827 | ||
|
|
f654982e38 | ||
|
|
38394ee263 | ||
|
|
c9010d4224 | ||
|
|
cb1b8024b4 | ||
|
|
075a0e99af | ||
|
|
5f4c86697a | ||
|
|
1fee06c843 | ||
|
|
23bc7a629b | ||
|
|
86ec94ca0d | ||
|
|
93f0ade107 | ||
|
|
40bb3d2750 | ||
|
|
86a9cb13d9 | ||
|
|
e1138ff7b5 | ||
|
|
52fe82b834 | ||
|
|
15e3c4f5e5 | ||
|
|
4e5853e29a | ||
|
|
34d0d143c5 | ||
|
|
d37cb22698 | ||
|
|
b5787407b6 | ||
|
|
4d1d79098d | ||
|
|
3fc0c1e20f | ||
|
|
726b8bfbe5 | ||
|
|
0306ab97f1 | ||
|
|
083df572b8 | ||
|
|
12c705476d | ||
|
|
4880d70129 | ||
|
|
f49d5e2352 | ||
|
|
f8b9a31bf2 | ||
|
|
808a43b44d | ||
|
|
f2c523c267 | ||
|
|
b847f7f8c9 | ||
|
|
a9f13ee503 | ||
|
|
e0b7ea4960 | ||
|
|
a41c0454fa | ||
|
|
226716c816 | ||
|
|
3c6f82294b | ||
|
|
600d672b4a | ||
|
|
48cb11566f | ||
|
|
c3be51c117 | ||
|
|
841808d99a | ||
|
|
258f0a2072 | ||
|
|
0b2e60b0c1 | ||
|
|
669bc9ceaa | ||
|
|
93d9d12879 | ||
|
|
9d0f3f325b | ||
|
|
d66b61f0a3 | ||
|
|
9d79b6c262 | ||
|
|
7721b227cb | ||
|
|
d1106f8df7 | ||
|
|
794fcdafe5 | ||
|
|
344126d5aa | ||
|
|
2a72a87ef4 | ||
|
|
7b1af4035d | ||
|
|
931d08d3ac | ||
|
|
85b2409a66 | ||
|
|
ea582088a6 | ||
|
|
468dd46205 | ||
|
|
b03f263fce | ||
|
|
15b69d3d70 | ||
|
|
8004892058 | ||
|
|
f03e386399 | ||
|
|
5b9d19cfce | ||
|
|
e7d5700dac | ||
|
|
5a133686ec | ||
|
|
58df46261e | ||
|
|
9bf7e08f01 | ||
|
|
40478a45ac | ||
|
|
3c8b8a04e1 | ||
|
|
82e520ca67 | ||
|
|
01a17b4b2c | ||
|
|
63f958fa60 | ||
|
|
f3d739a5d8 | ||
|
|
453f14760c | ||
|
|
9f19b3f93f | ||
|
|
cc5d2c47ee | ||
|
|
c916c4da31 | ||
|
|
9e79d7810f | ||
|
|
0dbbcd4edc | ||
|
|
eb039b3bdd | ||
|
|
f4dfa6038c | ||
|
|
9be6a5a6a3 | ||
|
|
81c06f230a | ||
|
|
645a11f9c3 | ||
|
|
68ca9ec622 | ||
|
|
b4883eb77e | ||
|
|
7e57ffb62d | ||
|
|
820666b278 | ||
|
|
9d0c2968aa | ||
|
|
d3f2b07c83 | ||
|
|
57ebad948d | ||
|
|
3f1b9df62a | ||
|
|
f990f19795 | ||
|
|
b2e1855949 | ||
|
|
850e54fbbc | ||
|
|
9931b33be7 | ||
|
|
040518d094 | ||
|
|
823c651532 | ||
|
|
b412a361b9 | ||
|
|
022ea1bb75 | ||
|
|
f08f7b09d5 | ||
|
|
16452dd11a | ||
|
|
b0791a0c6a | ||
|
|
2b8d826209 | ||
|
|
f37eee21cd | ||
|
|
e195fe40b9 | ||
|
|
900ba9ec5b | ||
|
|
b2526523d8 | ||
|
|
8db0281f91 | ||
|
|
9637091ab5 | ||
|
|
45771ec8c0 | ||
|
|
5c3ff8297e | ||
|
|
722f512e53 | ||
|
|
0cceaf0485 | ||
|
|
23d0d70478 | ||
|
|
2eea49a8e4 | ||
|
|
c96ded2508 | ||
|
|
f0a6760312 | ||
|
|
55db217c74 | ||
|
|
11c5283014 | ||
|
|
bad3227e4f | ||
|
|
7ea0a5268c | ||
|
|
456083d2b0 | ||
|
|
ad0766d9a6 | ||
|
|
e1dbdcc051 | ||
|
|
a7d8a62a69 | ||
|
|
c8fdc6f272 | ||
|
|
9990062ec1 | ||
|
|
6817b4af66 | ||
|
|
133f1656d8 | ||
|
|
599d75351c | ||
|
|
c42202a140 | ||
|
|
d6c0cf6ef8 | ||
|
|
72ad7804ca | ||
|
|
11e33bfaa0 | ||
|
|
b1ad316134 | ||
|
|
f2dc8e3e80 | ||
|
|
3ff20effc8 | ||
|
|
eb4ae865d4 | ||
|
|
6bf1af44d8 | ||
|
|
a74f1a1427 | ||
|
|
df88cb970f | ||
|
|
452dee7e11 | ||
|
|
f6a6c33193 | ||
|
|
6a98c8f371 | ||
|
|
9546594e8d | ||
|
|
23f5b7696a | ||
|
|
93ae66ad39 | ||
|
|
4d3ce706a5 | ||
|
|
5b57ef3531 | ||
|
|
eadc0afe1a | ||
|
|
687b5f18ef | ||
|
|
25178869ae | ||
|
|
eedcad8280 | ||
|
|
50c94755fb | ||
|
|
ec79434759 | ||
|
|
4192d56909 | ||
|
|
2c82aedb74 | ||
|
|
049ed5f9c4 | ||
|
|
b183862e8d | ||
|
|
57bc3f930a | ||
|
|
48a3b4d652 | ||
|
|
bee93cbab3 | ||
|
|
9d0a683db8 | ||
|
|
e163132a1e | ||
|
|
d35cb8de32 | ||
|
|
162a27f5a1 | ||
|
|
d1c2096024 | ||
|
|
50c8e129d0 | ||
|
|
01bd5e5d54 | ||
|
|
562a9e1f8d | ||
|
|
548811b370 | ||
|
|
6ae29a311a | ||
|
|
9934e20f8b | ||
|
|
e7a5ee57f1 | ||
|
|
06f2f416e0 | ||
|
|
8bda30179e | ||
|
|
9806c2c63c | ||
|
|
9d5d6dd6d2 | ||
|
|
8fecf07c47 | ||
|
|
a539d9bf5a | ||
|
|
ddc531c5f2 | ||
|
|
102b366c1e | ||
|
|
26fb509cb9 | ||
|
|
81e616ad16 | ||
|
|
c140ae7647 | ||
|
|
ef0a1a843d | ||
|
|
bf200de73b | ||
|
|
9a56fb8998 | ||
|
|
4a3740dac7 | ||
|
|
31145ef2eb | ||
|
|
23ab256f7d | ||
|
|
0f435b58a1 | ||
|
|
5d1a0421fd | ||
|
|
55ed4c1bc0 | ||
|
|
22a6a72f12 | ||
|
|
7c38ac7dfa | ||
|
|
d29dc72831 | ||
|
|
e062c83c02 | ||
|
|
c25971bba9 | ||
|
|
92a11d801c | ||
|
|
0ce636c6b4 | ||
|
|
65969d4ae2 | ||
|
|
0ba3cdfe87 | ||
|
|
5dd0edb317 | ||
|
|
2bb6379232 | ||
|
|
ebdd532343 | ||
|
|
ac97774639 | ||
|
|
f6f690ae01 | ||
|
|
87e6b38fa5 | ||
|
|
2dcc56934d | ||
|
|
f5cb80cec5 | ||
|
|
1d71b1efea | ||
|
|
62e206f8ed | ||
|
|
0de140538c | ||
|
|
55167c5dca | ||
|
|
9c78dbfde4 | ||
|
|
0ca0458656 | ||
|
|
1b86738d10 | ||
|
|
ccfbaf5b3d | ||
|
|
16ecae29f0 | ||
|
|
7a9fb7a4d8 | ||
|
|
aef84aed51 | ||
|
|
a8b1ce3c32 | ||
|
|
7dde40beff | ||
|
|
ccf39adde4 | ||
|
|
5b37a6fb53 | ||
|
|
942225291f | ||
|
|
4787784d15 | ||
|
|
093f157f77 | ||
|
|
78f3614c0a | ||
|
|
ae060a1b76 | ||
|
|
244b3d6d24 | ||
|
|
01bf10c719 | ||
|
|
5718072f60 | ||
|
|
1dfd977238 | ||
|
|
9030a39d1d | ||
|
|
d74a4143f3 | ||
|
|
f6509dd30e | ||
|
|
6c512dd451 | ||
|
|
00c83f1d25 | ||
|
|
28615040e8 | ||
|
|
0482d4636b | ||
|
|
86de537fcb | ||
|
|
fb7fdb65f2 | ||
|
|
15c36118d6 | ||
|
|
092eae52fe | ||
|
|
ac58058938 | ||
|
|
64117ad1fc | ||
|
|
317833af6d | ||
|
|
54d367ae8c | ||
|
|
1381de7d55 | ||
|
|
caacde3aad | ||
|
|
49e55f1326 | ||
|
|
5082da429f | ||
|
|
ea4e821f51 | ||
|
|
83c0195648 | ||
|
|
5c465f8bf0 | ||
|
|
c670d175d9 | ||
|
|
415e2a1623 | ||
|
|
dff5962b8e | ||
|
|
d7febae2e1 | ||
|
|
a757e033d9 | ||
|
|
c3b05157c5 | ||
|
|
cb3430b806 | ||
|
|
a86dad40c3 | ||
|
|
04066b9bc2 | ||
|
|
6580883ccc | ||
|
|
fa30bbe67c | ||
|
|
e39b70a5d8 | ||
|
|
45f33106f2 | ||
|
|
415f911e46 | ||
|
|
7b4b869992 | ||
|
|
3d57759d73 | ||
|
|
ad0bec318f | ||
|
|
9571b2695f | ||
|
|
f09bcc4a66 | ||
|
|
fc424b1bdd | ||
|
|
8a9b7c562c | ||
|
|
4d9c730f6f | ||
|
|
9a1223c228 | ||
|
|
d7787dc974 | ||
|
|
a3f30d899b | ||
|
|
ca48e75a9d | ||
|
|
e5051d2635 | ||
|
|
c5a6145ed7 | ||
|
|
b2398ec8e0 | ||
|
|
1b739ab6e5 | ||
|
|
2e20a88b3b | ||
|
|
9d6798e698 | ||
|
|
f3078059b2 | ||
|
|
fe2c3c90d6 | ||
|
|
b994bce116 | ||
|
|
f86d6fc2a8 | ||
|
|
747fa3eb3c | ||
|
|
f723f3347a | ||
|
|
8d1367461d | ||
|
|
3fd3b2d642 | ||
|
|
0f853983c9 | ||
|
|
1d20aeddda | ||
|
|
ba10aa935a | ||
|
|
56c63bc6d1 | ||
|
|
ef70434277 | ||
|
|
313112a488 | ||
|
|
9030939938 | ||
|
|
c87fdb820b | ||
|
|
fd260af645 | ||
|
|
f7904aac2b | ||
|
|
c338988650 | ||
|
|
07c4e1b57b | ||
|
|
a5b58aa3ae | ||
|
|
31fc6e3650 | ||
|
|
75dc2674d6 | ||
|
|
f212cf1dc8 | ||
|
|
0a3f1bf43c | ||
|
|
1039657a82 | ||
|
|
43299a5298 | ||
|
|
5f8f82fc93 | ||
|
|
171cc23dba | ||
|
|
9e5e05ae3c | ||
|
|
e67b63375c | ||
|
|
dda23854a3 | ||
|
|
625673e167 | ||
|
|
d8845a413d | ||
|
|
30f80f75df | ||
|
|
c4c9d5e042 | ||
|
|
b56de30ea2 | ||
|
|
2782046bed | ||
|
|
116fa84b16 | ||
|
|
bdb10fe59c | ||
|
|
7aa93d61b3 | ||
|
|
74ed34b6dd | ||
|
|
faf9ace2ab | ||
|
|
1602a1e78a | ||
|
|
c6b0a0e072 | ||
|
|
311eaa70e3 | ||
|
|
659b1c7a63 | ||
|
|
c29432cef2 | ||
|
|
7256cc2f8f | ||
|
|
a9693495d7 | ||
|
|
feae4ebf81 | ||
|
|
e97ca38cbe | ||
|
|
eff1be31da | ||
|
|
9ee07411d0 | ||
|
|
a0e94f2f51 | ||
|
|
be99725251 | ||
|
|
25e9b942b1 | ||
|
|
ac041a79f6 | ||
|
|
c59d99b8ae | ||
|
|
3885e02d60 | ||
|
|
8b432cffd9 | ||
|
|
51e5ce0e17 | ||
|
|
f2f984aff6 | ||
|
|
a0d8a8a241 | ||
|
|
b8101d0387 | ||
|
|
55d02d5adb | ||
|
|
cf91b69d90 | ||
|
|
c2fdd86c29 | ||
|
|
81af887694 | ||
|
|
85ef3aaa23 | ||
|
|
c8610da04b | ||
|
|
490e204060 | ||
|
|
b2044944ff | ||
|
|
cbf7cc0b4c | ||
|
|
b9dbc14a4d | ||
|
|
d1364066db | ||
|
|
72d16f0636 | ||
|
|
3aa43b8246 | ||
|
|
ad288f5303 | ||
|
|
fcc96cf303 | ||
|
|
ca1504b839 | ||
|
|
e265cb84cc | ||
|
|
6452189938 | ||
|
|
3d7bd388e1 | ||
|
|
21cfcac0c9 | ||
|
|
5eae6acae0 | ||
|
|
1c0cb3a885 | ||
|
|
b43fbcfabc | ||
|
|
76146d3c6b | ||
|
|
641a443c82 | ||
|
|
e1027ce7ef | ||
|
|
46422a163b | ||
|
|
556d8bfd48 | ||
|
|
5b8e709271 | ||
|
|
6fce5cc5a4 | ||
|
|
b504b60112 | ||
|
|
c8b4e574b8 | ||
|
|
32f117c7ec | ||
|
|
95d3642d2e | ||
|
|
c2ed631327 | ||
|
|
1f90f4e470 | ||
|
|
d397db9cfb | ||
|
|
b5616362b0 | ||
|
|
5d2a32c114 | ||
|
|
5a64b6c297 | ||
|
|
095e720435 | ||
|
|
6e4908acc3 | ||
|
|
b053f79de3 | ||
|
|
33564d023c | ||
|
|
57f445b791 | ||
|
|
f79e3cad2a | ||
|
|
0d8a2d4a1d | ||
|
|
2df1770781 | ||
|
|
51ec73bdf9 | ||
|
|
4299db4041 | ||
|
|
c3a95a07a1 | ||
|
|
38d614a567 | ||
|
|
c0d77a7fdf | ||
|
|
806d62129b | ||
|
|
dffd0cd62c | ||
|
|
333a96bb8d | ||
|
|
1aadc7991f | ||
|
|
a41dc4bbd9 | ||
|
|
6529e02c83 | ||
|
|
476c2e4ce3 | ||
|
|
706055f1b5 | ||
|
|
18c964ee3c | ||
|
|
2a6ba11552 | ||
|
|
ef9fb6ea68 | ||
|
|
7055c11d16 | ||
|
|
b0bc918bc1 | ||
|
|
bc030355fc | ||
|
|
873c748e76 | ||
|
|
9a987ec7a3 | ||
|
|
b7d2790a8b | ||
|
|
98c7b47681 | ||
|
|
992c81263a | ||
|
|
d9e9076fb4 | ||
|
|
938d5259a4 | ||
|
|
8d64694ac7 | ||
|
|
1a55341dd3 | ||
|
|
3f2d70c315 | ||
|
|
fe005f3232 | ||
|
|
38f573bfce | ||
|
|
efc2d3a8e6 | ||
|
|
b2f0f0ca84 | ||
|
|
fd347c10d0 | ||
|
|
0aacc3e2da | ||
|
|
fea69dfe06 | ||
|
|
e8e45cc378 | ||
|
|
5d6047ca7d | ||
|
|
6ad5e6d974 | ||
|
|
82ced80a3e | ||
|
|
79347c0f9c | ||
|
|
7b060320b5 | ||
|
|
38f4d4aa1b | ||
|
|
3dc80a8342 | ||
|
|
8dbfd9050c | ||
|
|
c5604bddac | ||
|
|
789401ed24 | ||
|
|
c0d967a467 | ||
|
|
75d49554da | ||
|
|
d228429b56 | ||
|
|
9630d1a26b | ||
|
|
2922f99ae3 | ||
|
|
cf7a7177c7 | ||
|
|
f5dcbf4e6e | ||
|
|
60c946a501 | ||
|
|
28393d4571 | ||
|
|
eacfa96df4 | ||
|
|
576f83b5dc | ||
|
|
b35ea79083 | ||
|
|
45f2193e2a | ||
|
|
b329786659 | ||
|
|
adf181aaec | ||
|
|
a95fff5ae3 | ||
|
|
f401c3900c | ||
|
|
1f01e2ae16 | ||
|
|
d5cc3289dc | ||
|
|
9a0ba159b0 | ||
|
|
71dfe3146b | ||
|
|
9ed01ebbb6 | ||
|
|
0e7af09df3 | ||
|
|
3c4844e46d | ||
|
|
965b67577a | ||
|
|
8bcb38d4fc | ||
|
|
071873718e | ||
|
|
7583b67bda | ||
|
|
96d3ddf9b5 | ||
|
|
dc0ddf542b | ||
|
|
724e483ae3 | ||
|
|
910fef3ecd | ||
|
|
65288905d0 | ||
|
|
82d7171a92 | ||
|
|
20b51f59b0 | ||
|
|
16c06c7d7d | ||
|
|
0aa20cb3f4 | ||
|
|
71787b7059 | ||
|
|
a9407467a5 | ||
|
|
896eb79d2c | ||
|
|
fb6f34d67e | ||
|
|
9e9570d191 | ||
|
|
1b130ab157 | ||
|
|
74005529f5 | ||
|
|
4186a365cb | ||
|
|
4366db1acc | ||
|
|
4679e00a9e | ||
|
|
51960f0e28 | ||
|
|
a27f69b1c5 | ||
|
|
8ab01cce3e | ||
|
|
e10235aae9 | ||
|
|
05591c6bd9 | ||
|
|
a25e469291 | ||
|
|
ebd14944c6 | ||
|
|
1b08fc3592 | ||
|
|
57457946e5 | ||
|
|
918a39ab60 | ||
|
|
2cc39c2580 | ||
|
|
3aca94dc9b | ||
|
|
65ad9d61d9 | ||
|
|
138e3a4661 | ||
|
|
41844ee7e3 | ||
|
|
d4f2594e26 | ||
|
|
babc9de575 | ||
|
|
11a5683025 | ||
|
|
b6f8f89c7a | ||
|
|
0885fe5898 | ||
|
|
8aa1f049ab | ||
|
|
f7aa7bf580 | ||
|
|
b74033fe17 | ||
|
|
596296cabf | ||
|
|
adcd86e7d7 | ||
|
|
308768ae50 | ||
|
|
235636ca4a | ||
|
|
359ce5e39d | ||
|
|
c6e13e98b3 | ||
|
|
0cc9687e39 | ||
|
|
3d59f1832c | ||
|
|
58fdd58e9a | ||
|
|
ea661c4fbb | ||
|
|
28abe52106 | ||
|
|
e689fd8721 | ||
|
|
fea6c85e97 | ||
|
|
4e80a1b818 | ||
|
|
bdde3520f4 | ||
|
|
165ae649b1 | ||
|
|
0536ebf1c8 | ||
|
|
fb176abe05 | ||
|
|
28f0232736 | ||
|
|
4c29fd8faf | ||
|
|
df8d289ee0 | ||
|
|
203de48dd4 | ||
|
|
97f763b1a0 | ||
|
|
9fd8969ea5 | ||
|
|
d70e699c81 | ||
|
|
39bf8f5313 | ||
|
|
68eb8e2510 | ||
|
|
a20800cfb3 | ||
|
|
adce811a3d | ||
|
|
084a7ae73e | ||
|
|
14a697269b | ||
|
|
7de8cdee90 | ||
|
|
20333491f7 | ||
|
|
d529c55277 | ||
|
|
9e3bb1ca72 | ||
|
|
0caf41f1d6 | ||
|
|
817aadb995 | ||
|
|
c6016c2728 | ||
|
|
492cab9c06 | ||
|
|
64dd84b560 | ||
|
|
496768b566 | ||
|
|
b2fcc6e743 | ||
|
|
1584fcbb82 | ||
|
|
2860111154 | ||
|
|
cfdbb98cf9 | ||
|
|
87ec3a528b | ||
|
|
8ebabd978f | ||
|
|
51fae70e24 | ||
|
|
2f0024a8dd | ||
|
|
cd498e408c | ||
|
|
6d193c315e | ||
|
|
c81d9f6bc2 | ||
|
|
e13e5d0b16 | ||
|
|
ceb892d610 | ||
|
|
2690a4d548 | ||
|
|
59a8e550d6 | ||
|
|
4793425c58 | ||
|
|
e47ee567bf | ||
|
|
c9f33415e1 | ||
|
|
d1a0c9f1ff | ||
|
|
40b43b00c2 | ||
|
|
cea6d3a9df | ||
|
|
ecda5377a3 | ||
|
|
fb9c7290b2 | ||
|
|
5b185d2fe3 | ||
|
|
c4712e705c | ||
|
|
d4656b619f | ||
|
|
e8c5a58d85 | ||
|
|
143cfa04de | ||
|
|
ef752c5db6 | ||
|
|
f4ba889c84 | ||
|
|
f20e61520b | ||
|
|
88f082e261 | ||
|
|
6304fa4091 | ||
|
|
4b01074d0f | ||
|
|
65c8e47e26 | ||
|
|
80c6e30691 | ||
|
|
8cfc922d97 | ||
|
|
92e6c303ad | ||
|
|
762dc60600 | ||
|
|
373b81072a | ||
|
|
9bf2d57715 | ||
|
|
ef71a7befc | ||
|
|
dc34c4e985 | ||
|
|
9a495e516a | ||
|
|
6fcd744132 | ||
|
|
70bc280cf1 | ||
|
|
470fe4b877 | ||
|
|
86f472d29f | ||
|
|
b54ce90df1 | ||
|
|
db270d0c7c | ||
|
|
9d5837bb87 | ||
|
|
12848af745 | ||
|
|
2d28139ca0 | ||
|
|
272663dc4a | ||
|
|
d4a47260cb | ||
|
|
7023521fb2 | ||
|
|
97fe8dc35b | ||
|
|
fb0c0bd807 | ||
|
|
2285541b6d | ||
|
|
e965b899c0 | ||
|
|
011811d183 | ||
|
|
65efbc57f3 | ||
|
|
437587766b | ||
|
|
a1fe5c5db1 | ||
|
|
c44d91281e | ||
|
|
56fdf15b6f | ||
|
|
19be99d106 | ||
|
|
2e25c83775 | ||
|
|
dae91de27e | ||
|
|
15424d2fea | ||
|
|
cd1d744e1f | ||
|
|
dc77591571 | ||
|
|
074467e65e | ||
|
|
c81f93551e | ||
|
|
1f6cbd7d19 | ||
|
|
bd35e224a4 | ||
|
|
ae5986f101 | ||
|
|
5cbf5649c0 | ||
|
|
3da4db3edc | ||
|
|
47121019d4 | ||
|
|
c97b390161 | ||
|
|
f97312dcf2 | ||
|
|
0eed744018 | ||
|
|
655737dcb5 | ||
|
|
488fa4a4dd | ||
|
|
8344522e12 | ||
|
|
e1e085d610 | ||
|
|
e35904e727 | ||
|
|
6dc8d7648a | ||
|
|
c296b32209 | ||
|
|
69f2bb70e8 | ||
|
|
a499e0a551 | ||
|
|
bc99962621 | ||
|
|
bf9b7054b3 | ||
|
|
50be973e57 | ||
|
|
108c39ef7c | ||
|
|
ae6ea42aa9 | ||
|
|
6c4ceccc30 | ||
|
|
fde7d6ff09 | ||
|
|
1af3505d0b | ||
|
|
047f018da4 | ||
|
|
a90e9f05e2 | ||
|
|
7db7e10934 | ||
|
|
397b340701 | ||
|
|
a050b584ac | ||
|
|
d68c740e1d | ||
|
|
b807ce30c4 | ||
|
|
a4bd13d050 | ||
|
|
244960d0d7 | ||
|
|
267c8c6b0d | ||
|
|
06fc409234 | ||
|
|
77ed24a203 | ||
|
|
ed3c519404 | ||
|
|
9fcf8e82ef | ||
|
|
dd4df9baaa | ||
|
|
4384b1621b | ||
|
|
0e1c82a31f | ||
|
|
b5babd5451 | ||
|
|
5cb8502566 | ||
|
|
b7c4720030 | ||
|
|
074243891c | ||
|
|
1117ff1e7c | ||
|
|
5dd24341e1 | ||
|
|
19d4f36faa | ||
|
|
ac9f8e3191 | ||
|
|
9d193fb3bb | ||
|
|
c20b0c3620 | ||
|
|
8409a7e6d1 | ||
|
|
3d59118b94 | ||
|
|
e0fb373b3d | ||
|
|
f0c8deb25e | ||
|
|
b288c9f61a | ||
|
|
823c278cd7 | ||
|
|
ba4ba8d051 | ||
|
|
467268c451 | ||
|
|
3f1f8321d3 | ||
|
|
5730a407c3 | ||
|
|
22974f8bf4 | ||
|
|
6414347d91 | ||
|
|
113f891348 | ||
|
|
cbab3e6000 | ||
|
|
17663bd2c0 | ||
|
|
c226a4dbd5 | ||
|
|
0c2d0e6cb6 | ||
|
|
d63106a71a | ||
|
|
2c26e3f509 | ||
|
|
50e0387cda | ||
|
|
b05dc40a1c | ||
|
|
3846be2321 | ||
|
|
2fa3179a31 | ||
|
|
2617ff339d | ||
|
|
e36311db44 | ||
|
|
2449166c36 | ||
|
|
eb16486ee6 | ||
|
|
e0c858a3d6 | ||
|
|
71749cef3f | ||
|
|
15839e343e | ||
|
|
9a98b39e94 | ||
|
|
9c6c0f1861 | ||
|
|
318d919111 | ||
|
|
c753a1c875 | ||
|
|
e304337a62 | ||
|
|
3768ca8f77 | ||
|
|
12f76827e9 | ||
|
|
3c5f464039 | ||
|
|
9f2304b1a5 | ||
|
|
8a0ba4f27a | ||
|
|
43dc6d35c0 | ||
|
|
d04c593adc | ||
|
|
4e1b7e886f | ||
|
|
8544e805bb | ||
|
|
7e18cbca97 | ||
|
|
f5734bb626 | ||
|
|
08823bd94c | ||
|
|
04cfc7a81f | ||
|
|
91656fd7c0 | ||
|
|
e984bd5708 | ||
|
|
eb0c0f3f84 | ||
|
|
acac0ee5e2 | ||
|
|
19b7daeaee | ||
|
|
c933e722f3 | ||
|
|
1536377eb2 | ||
|
|
a11640cbc9 | ||
|
|
d51fe454b5 | ||
|
|
550fd937f1 | ||
|
|
bd1add4d47 | ||
|
|
944eb38002 | ||
|
|
ddf481f4fd | ||
|
|
2e7d84b58f | ||
|
|
db853cb5ba | ||
|
|
d0d652b427 | ||
|
|
231e048936 | ||
|
|
e95982a043 | ||
|
|
1a7b2677f3 | ||
|
|
b77bb5768d | ||
|
|
2be9fc65b1 | ||
|
|
c4e04fe003 | ||
|
|
964e06b8cb | ||
|
|
78d6847db5 | ||
|
|
25d6ce6763 | ||
|
|
fc26b5d3a9 | ||
|
|
9e4dc631e8 | ||
|
|
b6bff03ad3 | ||
|
|
394a365497 | ||
|
|
d5de2f387b | ||
|
|
5106de0abe | ||
|
|
510c81d251 | ||
|
|
b4437636a8 | ||
|
|
cc64997832 | ||
|
|
c35afecce7 | ||
|
|
2d9e8cc2af | ||
|
|
a9c4842b96 | ||
|
|
c8b21a12ed | ||
|
|
d0a75c0943 | ||
|
|
c75d3b2d04 | ||
|
|
50d8c03f8b | ||
|
|
dde3e08e6a | ||
|
|
2dd3385849 | ||
|
|
bd6cc759c7 | ||
|
|
39f695f120 | ||
|
|
61839df123 | ||
|
|
e6b3ec048a | ||
|
|
4fb5e7d305 | ||
|
|
8d4a80089e | ||
|
|
539e321c53 | ||
|
|
737700719a | ||
|
|
b2bc26142a | ||
|
|
9a125bf4f5 | ||
|
|
b53135d83a | ||
|
|
86262f3810 | ||
|
|
9237de38df | ||
|
|
18d5d7da27 | ||
|
|
cba653d891 | ||
|
|
9ea265625e | ||
|
|
d4aa0ecae4 | ||
|
|
bb5ee52133 | ||
|
|
33d5d45d3c | ||
|
|
87bad70939 | ||
|
|
047bc9c5cd | ||
|
|
f098e333d2 | ||
|
|
785835632d | ||
|
|
360b72fd91 | ||
|
|
19082049e7 | ||
|
|
eacc29672f | ||
|
|
c7b6ac1670 | ||
|
|
0aa54cea23 | ||
|
|
9fe50f6c66 | ||
|
|
2bb84cf79f | ||
|
|
8d0ade1eab | ||
|
|
242c7c3b21 | ||
|
|
ff272b89fd | ||
|
|
4e83e73dca | ||
|
|
7d1ffa4e90 | ||
|
|
aab83d882a | ||
|
|
4f69b673bf | ||
|
|
7d17e58e1f | ||
|
|
3d6677bbd5 | ||
|
|
30d57a5745 | ||
|
|
2fb2b64d33 | ||
|
|
1dc8fed6e8 | ||
|
|
44c6605804 | ||
|
|
560f8ac14a | ||
|
|
b99dea604d | ||
|
|
f3ff419462 | ||
|
|
d79c2469ec | ||
|
|
94de9e7cf2 | ||
|
|
a9d9c800b8 | ||
|
|
678d09558c | ||
|
|
831edab34b | ||
|
|
5b64b41f21 | ||
|
|
78a5827976 | ||
|
|
a9b6faeb7c | ||
|
|
bebc58d91d | ||
|
|
2c70b8b7e0 | ||
|
|
07d7b7cb58 | ||
|
|
4c80dd3c42 | ||
|
|
0b436479be | ||
|
|
036cd90516 | ||
|
|
f0076117df | ||
|
|
f8b4a843c5 | ||
|
|
5a24092dc0 | ||
|
|
bf1c32f12a | ||
|
|
f0fb6539ef | ||
|
|
a85a761bee | ||
|
|
2c61dc9a75 | ||
|
|
9516929495 | ||
|
|
5350a4c476 | ||
|
|
26dc246c52 | ||
|
|
ad6716f00e | ||
|
|
dffb63d95f | ||
|
|
5069c71dd8 | ||
|
|
287abb9f37 | ||
|
|
7f8325fb6c | ||
|
|
6c38674716 | ||
|
|
54fdb69e88 | ||
|
|
068cee2326 | ||
|
|
e9c2e1034c | ||
|
|
f453c2af78 | ||
|
|
cc3c631cad | ||
|
|
7cfd060ed8 | ||
|
|
c77f5e9b61 | ||
|
|
f18bd75257 | ||
|
|
555a1f8411 | ||
|
|
c600dc3b4f | ||
|
|
0f138a1bf5 | ||
|
|
1c19f36637 | ||
|
|
d19973208c | ||
|
|
0a9078a2ec | ||
|
|
bf2368ee44 | ||
|
|
d0a23fbf14 | ||
|
|
015f561740 | ||
|
|
cff4a29f1c | ||
|
|
f50ee0ba36 | ||
|
|
fe5d8e537c | ||
|
|
d943891e4f | ||
|
|
da3d1d7099 | ||
|
|
86dd21ae7b | ||
|
|
2d30184f76 | ||
|
|
271063934a | ||
|
|
3fb601adfb | ||
|
|
757e11af42 | ||
|
|
7ec12fb58d | ||
|
|
6c9cf02884 | ||
|
|
0c904e208a | ||
|
|
d12a898333 | ||
|
|
0e665dc673 | ||
|
|
02299a0400 | ||
|
|
86d7069626 | ||
|
|
776b865761 | ||
|
|
a3a4ef22df | ||
|
|
c2db78baf7 | ||
|
|
9a30d78f54 | ||
|
|
db98ce160b | ||
|
|
ce8b5b53e0 | ||
|
|
0c4055726c | ||
|
|
64ae4bae0c | ||
|
|
044cc793e8 | ||
|
|
ca81b2b55c | ||
|
|
e310dc30d7 | ||
|
|
f239755249 | ||
|
|
57686c0554 | ||
|
|
5ecde572c3 | ||
|
|
23e9974950 | ||
|
|
ec98d128f1 | ||
|
|
5c07951604 | ||
|
|
7be34625f6 | ||
|
|
f15762b746 | ||
|
|
e365e78756 | ||
|
|
5f6cb893ef | ||
|
|
3b636be2fe | ||
|
|
1af940b034 | ||
|
|
8cb1789e60 | ||
|
|
01782de3a6 | ||
|
|
3a7e35c51d | ||
|
|
bff24a1d3d | ||
|
|
8a075000a2 | ||
|
|
5e0761d085 | ||
|
|
2184ddf1bb | ||
|
|
fd032f6ccd | ||
|
|
164c819523 | ||
|
|
987043ead2 | ||
|
|
4aa9c224d0 | ||
|
|
2ac6fa4542 | ||
|
|
2797f95cd3 | ||
|
|
5eb9be6248 | ||
|
|
23eebf9037 | ||
|
|
b59adab68d | ||
|
|
b2d44105be | ||
|
|
2235546f2a | ||
|
|
59d104f68b | ||
|
|
6abdfd391d | ||
|
|
a6ff0b5b10 | ||
|
|
02969cfe5b | ||
|
|
c6a2c35850 | ||
|
|
4c0a71f37d | ||
|
|
74e3bd1895 | ||
|
|
121c9f5012 | ||
|
|
7b1cd816b7 | ||
|
|
02ae8f8108 | ||
|
|
6a76cc8bc6 | ||
|
|
9b9c38126e | ||
|
|
e4ee0c768f | ||
|
|
340de153c8 | ||
|
|
2bf477102d | ||
|
|
42a15b40b3 | ||
|
|
a2589cd433 | ||
|
|
7ec7025ed4 | ||
|
|
5edeb6df94 | ||
|
|
001a1981cf | ||
|
|
446fb59473 | ||
|
|
3b58d6df42 | ||
|
|
a80c683901 | ||
|
|
a5b65eaaed | ||
|
|
31182289b7 | ||
|
|
85eb740264 | ||
|
|
4318e23a40 | ||
|
|
b1b01f2426 | ||
|
|
4ca7352d5c | ||
|
|
84905bd726 | ||
|
|
18e16368be | ||
|
|
7ed84c4c3f | ||
|
|
f334211395 | ||
|
|
164ca8541e | ||
|
|
42f4126aff | ||
|
|
5790e29daa | ||
|
|
88a4a2c6cf | ||
|
|
90b72fc11e | ||
|
|
00391df1f0 | ||
|
|
15de0c0bba | ||
|
|
a2fdce9be9 | ||
|
|
fa6fce9589 | ||
|
|
cc1f03fc94 | ||
|
|
8b25f6f129 | ||
|
|
b37937421c | ||
|
|
df7be19487 | ||
|
|
f17d2bf7fc | ||
|
|
3369db8150 | ||
|
|
408c9df2df | ||
|
|
63b6d665a1 | ||
|
|
cfc8b7fb28 | ||
|
|
1497345a79 | ||
|
|
bbd2940b63 | ||
|
|
b9c65fb8ee | ||
|
|
0b37b4e543 | ||
|
|
4c3e4f2170 | ||
|
|
dc12c50c1a | ||
|
|
2b986f5bac | ||
|
|
2accf7543e | ||
|
|
ba37189410 | ||
|
|
d578eee402 | ||
|
|
5107d89ef5 | ||
|
|
72b8b5c98e | ||
|
|
402885391f | ||
|
|
b4d153ab3b | ||
|
|
1a0cb62ac8 | ||
|
|
c0baf70c59 | ||
|
|
166cf2623c | ||
|
|
8567b69073 | ||
|
|
3a4271e66d | ||
|
|
8b723aebd9 | ||
|
|
eb59d6c86b | ||
|
|
a291b75dd9 | ||
|
|
f1a76e1e76 | ||
|
|
a391f7414f | ||
|
|
299de54ba5 | ||
|
|
37bae14dfd | ||
|
|
d780aa43d4 | ||
|
|
91534776d1 | ||
|
|
ef82e11c53 | ||
|
|
31bb611ecf | ||
|
|
762481e28a | ||
|
|
25194c7abe | ||
|
|
967af29c47 | ||
|
|
ae9ee57d79 | ||
|
|
17706bd5e4 | ||
|
|
9ed54f726c | ||
|
|
53c88f0302 | ||
|
|
ad1cc78578 | ||
|
|
f94a613eef | ||
|
|
e6fa89206a | ||
|
|
b010da744c | ||
|
|
d53eb3a3fe | ||
|
|
ea25ad2c3b | ||
|
|
ac7f34d3f5 | ||
|
|
4969547938 | ||
|
|
6808be7a42 | ||
|
|
70986149ef | ||
|
|
bc859b3e52 | ||
|
|
eb376b8eb3 | ||
|
|
9fce28bed7 | ||
|
|
0bcc0e9248 | ||
|
|
aecee3cea2 | ||
|
|
3432b9fdcd | ||
|
|
8bde4d5f92 | ||
|
|
67c596726f | ||
|
|
60fb1c0d02 | ||
|
|
3261e1a260 | ||
|
|
2e1fa8fa40 | ||
|
|
cadb2ae791 | ||
|
|
2d3990df81 | ||
|
|
23d4904ee7 | ||
|
|
f66f51b25e | ||
|
|
826ff27bd1 | ||
|
|
132aa8d49a | ||
|
|
67148cd84c | ||
|
|
941e3a7537 | ||
|
|
4487952c28 | ||
|
|
5089663b07 | ||
|
|
3ec5bfd4ff | ||
|
|
adb1ef35d9 | ||
|
|
df85278a44 | ||
|
|
7a04669560 | ||
|
|
2e24a1bde4 | ||
|
|
16134cb857 | ||
|
|
d396a8755b | ||
|
|
7617b95fae | ||
|
|
ecdda5b678 | ||
|
|
2a16a29ea2 | ||
|
|
81d7bec2cd | ||
|
|
87ffbe4c20 | ||
|
|
94a0fb20d2 | ||
|
|
7313670d8f | ||
|
|
5047e7732c | ||
|
|
98a459ac9c | ||
|
|
eec3f05dc7 | ||
|
|
4db2fd4ee7 | ||
|
|
41e0760678 | ||
|
|
36b9689cf8 | ||
|
|
c5b09815e3 | ||
|
|
3da7b0f8e6 | ||
|
|
1aa0d321cc |
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>1.6.30-SNAPSHOT</version>
|
<version>1.6.35</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-ai</artifactId>
|
<artifactId>forge-ai</artifactId>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import forge.ai.ability.AnimateAi;
|
|||||||
import forge.card.CardTypeView;
|
import forge.card.CardTypeView;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.ability.effects.ProtectEffect;
|
import forge.game.ability.effects.ProtectEffect;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
@@ -464,7 +465,7 @@ public class AiAttackController {
|
|||||||
final CardCollectionView beastions = ai.getCardsIn(ZoneType.Battlefield, "Beastmaster Ascension");
|
final CardCollectionView beastions = ai.getCardsIn(ZoneType.Battlefield, "Beastmaster Ascension");
|
||||||
int minCreatures = 7;
|
int minCreatures = 7;
|
||||||
for (final Card beastion : beastions) {
|
for (final Card beastion : beastions) {
|
||||||
final int counters = beastion.getCounters(CounterType.QUEST);
|
final int counters = beastion.getCounters(CounterEnumType.QUEST);
|
||||||
minCreatures = Math.min(minCreatures, 7 - counters);
|
minCreatures = Math.min(minCreatures, 7 - counters);
|
||||||
}
|
}
|
||||||
if (this.attackers.size() >= minCreatures) {
|
if (this.attackers.size() >= minCreatures) {
|
||||||
@@ -1065,7 +1066,7 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if enough damage: switch to next planeswalker or player
|
// if enough damage: switch to next planeswalker or player
|
||||||
if (damage >= pw.getCounters(CounterType.LOYALTY)) {
|
if (damage >= pw.getCounters(CounterEnumType.LOYALTY)) {
|
||||||
List<Card> pwDefending = combat.getDefendingPlaneswalkers();
|
List<Card> pwDefending = combat.getDefendingPlaneswalkers();
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
// look for next planeswalker
|
// look for next planeswalker
|
||||||
@@ -1135,7 +1136,6 @@ public class AiAttackController {
|
|||||||
// 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?)
|
// 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()) {
|
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!
|
// 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()) {
|
for (SpellAbility sa : attacker.getSpellAbilities()) {
|
||||||
// Do not attack if we can afford using the ability.
|
// Do not attack if we can afford using the ability.
|
||||||
if (sa.isAbility()) {
|
if (sa.isAbility()) {
|
||||||
@@ -1192,7 +1192,7 @@ public class AiAttackController {
|
|||||||
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
|
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
|
||||||
numberOfPossibleBlockers += 1;
|
numberOfPossibleBlockers += 1;
|
||||||
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false)
|
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false)
|
||||||
&& !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterType.P1P1) == 0)) {
|
&& !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterEnumType.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
|
||||||
@@ -1365,21 +1365,12 @@ public class AiAttackController {
|
|||||||
if (c.hasSVar("AIExertCondition")) {
|
if (c.hasSVar("AIExertCondition")) {
|
||||||
if (!c.getSVar("AIExertCondition").isEmpty()) {
|
if (!c.getSVar("AIExertCondition").isEmpty()) {
|
||||||
final String needsToExert = c.getSVar("AIExertCondition");
|
final String needsToExert = c.getSVar("AIExertCondition");
|
||||||
int x = 0;
|
|
||||||
int y = 0;
|
|
||||||
String sVar = needsToExert.split(" ")[0];
|
String sVar = needsToExert.split(" ")[0];
|
||||||
String comparator = needsToExert.split(" ")[1];
|
String comparator = needsToExert.split(" ")[1];
|
||||||
String compareTo = comparator.substring(2);
|
String compareTo = comparator.substring(2);
|
||||||
try {
|
|
||||||
x = Integer.parseInt(sVar);
|
int x = AbilityUtils.calculateAmount(c, sVar, null);
|
||||||
} catch (final NumberFormatException e) {
|
int y = AbilityUtils.calculateAmount(c, compareTo, null);
|
||||||
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)) {
|
if (Expressions.compare(x, comparator, y)) {
|
||||||
shouldExert = true;
|
shouldExert = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,9 +228,9 @@ 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(Keyword.UNDYING) && b.getCounters(CounterType.P1P1) == 0) || b.hasSVar("SacMe")
|
if ((b.hasKeyword(Keyword.UNDYING) && b.getCounters(CounterEnumType.P1P1) == 0) || b.hasSVar("SacMe")
|
||||||
|| (b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterType.TIME) == 1)
|
|| (b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterEnumType.TIME) == 1)
|
||||||
|| (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterType.FADE) == 0)
|
|| (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterEnumType.FADE) == 0)
|
||||||
|| b.hasSVar("EndOfTurnLeavePlay")) {
|
|| b.hasSVar("EndOfTurnLeavePlay")) {
|
||||||
blocker = b;
|
blocker = b;
|
||||||
break;
|
break;
|
||||||
@@ -299,8 +299,8 @@ 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.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterType.TIME) == 1)
|
if ((b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterEnumType.TIME) == 1)
|
||||||
|| (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterType.FADE) == 0)
|
|| (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterEnumType.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)) {
|
||||||
@@ -851,7 +851,7 @@ public class AiBlockController {
|
|||||||
damageToPW += ComputerUtilCombat.predictDamageTo((Card) def, pwatkr.getNetCombatDamage(), pwatkr, true);
|
damageToPW += ComputerUtilCombat.predictDamageTo((Card) def, pwatkr.getNetCombatDamage(), pwatkr, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((!onlyIfLethal && damageToPW > 0) || damageToPW >= def.getCounters(CounterType.LOYALTY)) {
|
if ((!onlyIfLethal && damageToPW > 0) || damageToPW >= def.getCounters(CounterEnumType.LOYALTY)) {
|
||||||
threatenedPWs.add((Card) def);
|
threatenedPWs.add((Card) def);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -909,7 +909,7 @@ public class AiBlockController {
|
|||||||
damageToPW += ComputerUtilCombat.predictDamageTo(pw, pwAtk.getNetCombatDamage(), pwAtk, true);
|
damageToPW += ComputerUtilCombat.predictDamageTo(pw, pwAtk.getNetCombatDamage(), pwAtk, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isFullyBlocked && damageToPW >= pw.getCounters(CounterType.LOYALTY)) {
|
if (!isFullyBlocked && damageToPW >= pw.getCounters(CounterEnumType.LOYALTY)) {
|
||||||
for (Card chump : pwDefenders) {
|
for (Card chump : pwDefenders) {
|
||||||
if (chosenChumpBlockers.contains(chump)) {
|
if (chosenChumpBlockers.contains(chump)) {
|
||||||
combat.removeFromCombat(chump);
|
combat.removeFromCombat(chump);
|
||||||
@@ -1281,6 +1281,7 @@ public class AiBlockController {
|
|||||||
oppCreatureCount = ComputerUtil.countUsefulCreatures(attackersLeft.get(0).getController());
|
oppCreatureCount = ComputerUtil.countUsefulCreatures(attackersLeft.get(0).getController());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (attacker != null && attacker.getOwner() != null)
|
||||||
if (attacker.getOwner().equals(ai) && "6".equals(attacker.getSVar("SacMe"))) {
|
if (attacker.getOwner().equals(ai) && "6".equals(attacker.getSVar("SacMe"))) {
|
||||||
// Temporarily controlled object - don't trade with it
|
// Temporarily controlled object - don't trade with it
|
||||||
// TODO: find a more reliable way to figure out that control will be reestablished next turn
|
// TODO: find a more reliable way to figure out that control will be reestablished next turn
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ public class AiController {
|
|||||||
&& CardFactoryUtil.isCounterable(host)) {
|
&& CardFactoryUtil.isCounterable(host)) {
|
||||||
return true;
|
return true;
|
||||||
} else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)
|
} else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)
|
||||||
&& host.getCMC() == c.getCounters(CounterType.CHARGE)) {
|
&& host.getCMC() == c.getCounters(CounterEnumType.CHARGE)) {
|
||||||
return true;
|
return true;
|
||||||
} else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)) {
|
} else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)) {
|
||||||
String hostName = host.getName();
|
String hostName = host.getName();
|
||||||
@@ -769,7 +769,7 @@ public class AiController {
|
|||||||
return AiPlayDecision.CantPlayAi;
|
return AiPlayDecision.CantPlayAi;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (sa.getPayCosts() != null){
|
else {
|
||||||
Cost payCosts = sa.getPayCosts();
|
Cost payCosts = sa.getPayCosts();
|
||||||
ManaCost mana = payCosts.getTotalMana();
|
ManaCost mana = payCosts.getTotalMana();
|
||||||
if (mana != null) {
|
if (mana != null) {
|
||||||
@@ -858,7 +858,7 @@ public class AiController {
|
|||||||
int neededMana = 0;
|
int neededMana = 0;
|
||||||
boolean dangerousRecurringCost = false;
|
boolean dangerousRecurringCost = false;
|
||||||
|
|
||||||
Cost costWithBuyback = sa.getPayCosts() != null ? sa.getPayCosts().copy() : Cost.Zero;
|
Cost costWithBuyback = sa.getPayCosts().copy();
|
||||||
for (OptionalCostValue opt : GameActionUtil.getOptionalCostValues(sa)) {
|
for (OptionalCostValue opt : GameActionUtil.getOptionalCostValues(sa)) {
|
||||||
if (opt.getType() == OptionalCost.Buyback) {
|
if (opt.getType() == OptionalCost.Buyback) {
|
||||||
costWithBuyback.add(opt.getCost());
|
costWithBuyback.add(opt.getCost());
|
||||||
@@ -907,8 +907,8 @@ public class AiController {
|
|||||||
public int compare(final SpellAbility a, final SpellAbility b) {
|
public int compare(final SpellAbility a, final SpellAbility b) {
|
||||||
// sort from highest cost to lowest
|
// sort from highest cost to lowest
|
||||||
// we want the highest costs first
|
// we want the highest costs first
|
||||||
int a1 = a.getPayCosts() == null ? 0 : a.getPayCosts().getTotalMana().getCMC();
|
int a1 = a.getPayCosts().getTotalMana().getCMC();
|
||||||
int b1 = b.getPayCosts() == null ? 0 : b.getPayCosts().getTotalMana().getCMC();
|
int b1 = b.getPayCosts().getTotalMana().getCMC();
|
||||||
|
|
||||||
// deprioritize SAs explicitly marked as preferred to be activated last compared to all other SAs
|
// deprioritize SAs explicitly marked as preferred to be activated last compared to all other SAs
|
||||||
if (a.hasParam("AIActivateLast") && !b.hasParam("AIActivateLast")) {
|
if (a.hasParam("AIActivateLast") && !b.hasParam("AIActivateLast")) {
|
||||||
@@ -927,12 +927,12 @@ public class AiController {
|
|||||||
// deprioritize pump spells with pure energy cost (can be activated last,
|
// deprioritize pump spells with pure energy cost (can be activated last,
|
||||||
// since energy is generally scarce, plus can benefit e.g. Electrostatic Pummeler)
|
// since energy is generally scarce, plus can benefit e.g. Electrostatic Pummeler)
|
||||||
int a2 = 0, b2 = 0;
|
int a2 = 0, b2 = 0;
|
||||||
if (a.getApi() == ApiType.Pump && a.getPayCosts() != null && a.getPayCosts().getCostEnergy() != null) {
|
if (a.getApi() == ApiType.Pump && a.getPayCosts().getCostEnergy() != null) {
|
||||||
if (a.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
if (a.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||||
a2 = a.getPayCosts().getCostEnergy().convertAmount();
|
a2 = a.getPayCosts().getCostEnergy().convertAmount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (b.getApi() == ApiType.Pump && b.getPayCosts() != null && b.getPayCosts().getCostEnergy() != null) {
|
if (b.getApi() == ApiType.Pump && b.getPayCosts().getCostEnergy() != null) {
|
||||||
if (b.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
if (b.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||||
b2 = b.getPayCosts().getCostEnergy().convertAmount();
|
b2 = b.getPayCosts().getCostEnergy().convertAmount();
|
||||||
}
|
}
|
||||||
@@ -956,8 +956,7 @@ public class AiController {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.getHostCard().equals(b.getHostCard()) && a.getApi() == b.getApi()
|
if (a.getHostCard().equals(b.getHostCard()) && a.getApi() == b.getApi()) {
|
||||||
&& a.getPayCosts() != null && b.getPayCosts() != null) {
|
|
||||||
// Cheaper Spectacle costs should be preferred
|
// Cheaper Spectacle costs should be preferred
|
||||||
// FIXME: Any better way to identify that these are the same ability, one with Spectacle and one not?
|
// FIXME: Any better way to identify that these are the same ability, one with Spectacle and one not?
|
||||||
// (looks like it's not a full-fledged alternative cost as such, and is not processed with other alt costs)
|
// (looks like it's not a full-fledged alternative cost as such, and is not processed with other alt costs)
|
||||||
@@ -1479,7 +1478,7 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (SpellAbility sa : card.getSpellAbilities()) {
|
for (SpellAbility sa : card.getSpellAbilities()) {
|
||||||
if (sa.getPayCosts() != null && sa.isAbility()
|
if (sa.isAbility()
|
||||||
&& sa.getPayCosts().getCostMana() != null
|
&& sa.getPayCosts().getCostMana() != null
|
||||||
&& sa.getPayCosts().getCostMana().getMana().getCMC() > 0
|
&& sa.getPayCosts().getCostMana().getMana().getCMC() > 0
|
||||||
&& (!sa.getPayCosts().hasTapCost() || !isTapLand)
|
&& (!sa.getPayCosts().hasTapCost() || !isTapLand)
|
||||||
@@ -1802,7 +1801,7 @@ public class AiController {
|
|||||||
throw new UnsupportedOperationException("AI is not supposed to reach this code at the moment");
|
throw new UnsupportedOperationException("AI is not supposed to reach this code at the moment");
|
||||||
}
|
}
|
||||||
|
|
||||||
public CardCollection chooseCardsForEffect(CardCollectionView pool, SpellAbility sa, int min, int max, boolean isOptional) {
|
public CardCollection chooseCardsForEffect(CardCollectionView pool, SpellAbility sa, int min, int max, boolean isOptional, Map<String, Object> params) {
|
||||||
if (sa == null || sa.getApi() == null) {
|
if (sa == null || sa.getApi() == null) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
@@ -1835,7 +1834,7 @@ public class AiController {
|
|||||||
default:
|
default:
|
||||||
CardCollection editablePool = new CardCollection(pool);
|
CardCollection editablePool = new CardCollection(pool);
|
||||||
for (int i = 0; i < max; i++) {
|
for (int i = 0; i < max; i++) {
|
||||||
Card c = player.getController().chooseSingleEntityForEffect(editablePool, sa, null, isOptional);
|
Card c = player.getController().chooseSingleEntityForEffect(editablePool, sa, null, isOptional, params);
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
result.add(c);
|
result.add(c);
|
||||||
editablePool.remove(c);
|
editablePool.remove(c);
|
||||||
@@ -1986,6 +1985,35 @@ public class AiController {
|
|||||||
return MyRandom.getRandom().nextBoolean();
|
return MyRandom.getRandom().nextBoolean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean chooseEvenOdd(SpellAbility sa) {
|
||||||
|
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
|
if (aiLogic.equals("AlwaysEven")) {
|
||||||
|
return false; // false is Even
|
||||||
|
} else if (aiLogic.equals("AlwaysOdd")) {
|
||||||
|
return true; // true is Odd
|
||||||
|
} else if (aiLogic.equals("Random")) {
|
||||||
|
return MyRandom.getRandom().nextBoolean();
|
||||||
|
} else if (aiLogic.equals("CMCInHand")) {
|
||||||
|
CardCollectionView hand = sa.getActivatingPlayer().getCardsIn(ZoneType.Hand);
|
||||||
|
int numEven = CardLists.filter(hand, CardPredicates.evenCMC()).size();
|
||||||
|
int numOdd = CardLists.filter(hand, CardPredicates.oddCMC()).size();
|
||||||
|
return numOdd > numEven;
|
||||||
|
} else if (aiLogic.equals("CMCOppControls")) {
|
||||||
|
CardCollectionView hand = sa.getActivatingPlayer().getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||||
|
int numEven = CardLists.filter(hand, CardPredicates.evenCMC()).size();
|
||||||
|
int numOdd = CardLists.filter(hand, CardPredicates.oddCMC()).size();
|
||||||
|
return numOdd > numEven;
|
||||||
|
} else if (aiLogic.equals("CMCOppControlsByPower")) {
|
||||||
|
// TODO: improve this to check for how dangerous those creatures actually are relative to host card
|
||||||
|
CardCollectionView hand = sa.getActivatingPlayer().getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||||
|
int powerEven = Aggregates.sum(CardLists.filter(hand, CardPredicates.evenCMC()), Accessors.fnGetNetPower);
|
||||||
|
int powerOdd = Aggregates.sum(CardLists.filter(hand, CardPredicates.oddCMC()), Accessors.fnGetNetPower);
|
||||||
|
return powerOdd > powerEven;
|
||||||
|
}
|
||||||
|
return MyRandom.getRandom().nextBoolean(); // outside of any specific logic, choose randomly
|
||||||
|
}
|
||||||
|
|
||||||
public Card chooseCardToHiddenOriginChangeZone(ZoneType destination, List<ZoneType> origin, SpellAbility sa,
|
public Card chooseCardToHiddenOriginChangeZone(ZoneType destination, List<ZoneType> origin, SpellAbility sa,
|
||||||
CardCollection fetchList, Player player2, Player decider) {
|
CardCollection fetchList, Player player2, Player decider) {
|
||||||
if (useSimulation) {
|
if (useSimulation) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
@@ -16,12 +17,14 @@ import forge.game.card.CardCollectionView;
|
|||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.cost.*;
|
import forge.game.cost.*;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.SpellAbilityStackInstance;
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.Aggregates;
|
||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
|
|
||||||
@@ -104,6 +107,24 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
}
|
}
|
||||||
return PaymentDecision.card(randomSubset);
|
return PaymentDecision.card(randomSubset);
|
||||||
}
|
}
|
||||||
|
else if (type.equals("DifferentNames")) {
|
||||||
|
CardCollection differentNames = new CardCollection();
|
||||||
|
CardCollection discardMe = CardLists.filter(hand, CardPredicates.hasSVar("DiscardMe"));
|
||||||
|
while (c > 0) {
|
||||||
|
Card chosen;
|
||||||
|
if (!discardMe.isEmpty()) {
|
||||||
|
chosen = Aggregates.random(discardMe);
|
||||||
|
discardMe = CardLists.filter(discardMe, Predicates.not(CardPredicates.sharesNameWith(chosen)));
|
||||||
|
} else {
|
||||||
|
final Card worst = ComputerUtilCard.getWorstAI(hand);
|
||||||
|
chosen = worst != null ? worst : Aggregates.random(hand);
|
||||||
|
}
|
||||||
|
differentNames.add(chosen);
|
||||||
|
hand = CardLists.filter(hand, Predicates.not(CardPredicates.sharesNameWith(chosen)));
|
||||||
|
c--;
|
||||||
|
}
|
||||||
|
return PaymentDecision.card(differentNames);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
||||||
|
|
||||||
@@ -329,7 +350,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CardCollectionView topLib = player.getCardsIn(ZoneType.Library, c);
|
CardCollectionView topLib = player.getCardsIn(ZoneType.Library, c);
|
||||||
return topLib.size() < c ? null : PaymentDecision.card(topLib);
|
return topLib.size() < c ? null : PaymentDecision.number(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -494,7 +515,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
@Override
|
@Override
|
||||||
public boolean apply(Card card) {
|
public boolean apply(Card card) {
|
||||||
for (final SpellAbility sa : card.getSpellAbilities()) {
|
for (final SpellAbility sa : card.getSpellAbilities()) {
|
||||||
if (sa.isManaAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
if (sa.isManaAbility() && sa.getPayCosts().hasTapCost()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -579,7 +600,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
@Override
|
@Override
|
||||||
public PaymentDecision visit(CostReveal cost) {
|
public PaymentDecision visit(CostReveal cost) {
|
||||||
final String type = cost.getType();
|
final String type = cost.getType();
|
||||||
CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
|
CardCollectionView hand = player.getCardsIn(cost.getRevealFrom());
|
||||||
|
|
||||||
if (cost.payCostFromSource()) {
|
if (cost.payCostFromSource()) {
|
||||||
if (!hand.contains(source)) {
|
if (!hand.contains(source)) {
|
||||||
@@ -627,41 +648,41 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
// the first things are benefit from removing counters
|
// the first things are benefit from removing counters
|
||||||
|
|
||||||
// try to remove +1/+1 counter from undying creature
|
// try to remove +1/+1 counter from undying creature
|
||||||
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterType.P1P1, c),
|
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.P1P1, c),
|
||||||
CardPredicates.hasKeyword("Undying"));
|
CardPredicates.hasKeyword("Undying"));
|
||||||
|
|
||||||
if (!prefs.isEmpty()) {
|
if (!prefs.isEmpty()) {
|
||||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterType.P1P1));
|
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.P1P1));
|
||||||
PaymentDecision result = PaymentDecision.card(prefs);
|
PaymentDecision result = PaymentDecision.card(prefs);
|
||||||
result.ct = CounterType.P1P1;
|
result.ct = CounterType.get(CounterEnumType.P1P1);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to remove -1/-1 counter from persist creature
|
// try to remove -1/-1 counter from persist creature
|
||||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterType.M1M1, c),
|
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.M1M1, c),
|
||||||
CardPredicates.hasKeyword("Persist"));
|
CardPredicates.hasKeyword("Persist"));
|
||||||
|
|
||||||
if (!prefs.isEmpty()) {
|
if (!prefs.isEmpty()) {
|
||||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterType.M1M1));
|
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.M1M1));
|
||||||
PaymentDecision result = PaymentDecision.card(prefs);
|
PaymentDecision result = PaymentDecision.card(prefs);
|
||||||
result.ct = CounterType.M1M1;
|
result.ct = CounterType.get(CounterEnumType.M1M1);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to remove Time counter from Chronozoa, it will generate more
|
// try to remove Time counter from Chronozoa, it will generate more
|
||||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterType.TIME, c),
|
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.TIME, c),
|
||||||
CardPredicates.nameEquals("Chronozoa"));
|
CardPredicates.nameEquals("Chronozoa"));
|
||||||
|
|
||||||
if (!prefs.isEmpty()) {
|
if (!prefs.isEmpty()) {
|
||||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterType.TIME));
|
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.TIME));
|
||||||
PaymentDecision result = PaymentDecision.card(prefs);
|
PaymentDecision result = PaymentDecision.card(prefs);
|
||||||
result.ct = CounterType.TIME;
|
result.ct = CounterType.get(CounterEnumType.TIME);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to remove Quest counter on something with enough counters for the
|
// try to remove Quest counter on something with enough counters for the
|
||||||
// effect to continue
|
// effect to continue
|
||||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterType.QUEST, c));
|
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.QUEST, c));
|
||||||
|
|
||||||
if (!prefs.isEmpty()) {
|
if (!prefs.isEmpty()) {
|
||||||
prefs = CardLists.filter(prefs, new Predicate<Card>() {
|
prefs = CardLists.filter(prefs, new Predicate<Card>() {
|
||||||
@@ -673,12 +694,12 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
if (crd.hasSVar("MaxQuestEffect")) {
|
if (crd.hasSVar("MaxQuestEffect")) {
|
||||||
e = Integer.parseInt(crd.getSVar("MaxQuestEffect"));
|
e = Integer.parseInt(crd.getSVar("MaxQuestEffect"));
|
||||||
}
|
}
|
||||||
return crd.getCounters(CounterType.QUEST) >= e + c;
|
return crd.getCounters(CounterEnumType.QUEST) >= e + c;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Collections.sort(prefs, Collections.reverseOrder(CardPredicates.compareByCounterType(CounterType.QUEST)));
|
Collections.sort(prefs, Collections.reverseOrder(CardPredicates.compareByCounterType(CounterEnumType.QUEST)));
|
||||||
PaymentDecision result = PaymentDecision.card(prefs);
|
PaymentDecision result = PaymentDecision.card(prefs);
|
||||||
result.ct = CounterType.QUEST;
|
result.ct = CounterType.get(CounterEnumType.QUEST);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -775,7 +796,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card crd) {
|
public boolean apply(final Card crd) {
|
||||||
for (Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
for (Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||||
if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey() == CounterType.valueOf(ctr))) {
|
if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey().equals(CounterType.getType(ctr)))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -787,7 +808,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
PaymentDecision result = PaymentDecision.card(card);
|
PaymentDecision result = PaymentDecision.card(card);
|
||||||
|
|
||||||
for (Map.Entry<CounterType, Integer> e : card.getCounters().entrySet()) {
|
for (Map.Entry<CounterType, Integer> e : card.getCounters().entrySet()) {
|
||||||
if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey() == CounterType.valueOf(ctr))) {
|
if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey().equals(CounterType.getType(ctr)))) {
|
||||||
result.ct = e.getKey();
|
result.ct = e.getKey();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,9 +91,6 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
source.setCastSA(sa);
|
|
||||||
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
|
||||||
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
|
||||||
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +106,7 @@ public class ComputerUtil {
|
|||||||
if (chooseTargets != null) {
|
if (chooseTargets != null) {
|
||||||
chooseTargets.run();
|
chooseTargets.run();
|
||||||
}
|
}
|
||||||
if (sa.hasParam("Bestow")) {
|
if (sa.isBestow()) {
|
||||||
sa.getHostCard().animateBestow();
|
sa.getHostCard().animateBestow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,9 +216,6 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
if (sa.isSpell() && !source.isCopiedSpell()) {
|
if (sa.isSpell() && !source.isCopiedSpell()) {
|
||||||
source.setCastSA(sa);
|
|
||||||
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
|
||||||
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
|
||||||
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,9 +240,6 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
if (sa.isSpell() && !source.isCopiedSpell()) {
|
if (sa.isSpell() && !source.isCopiedSpell()) {
|
||||||
source.setCastSA(sa);
|
|
||||||
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
|
||||||
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
|
||||||
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,9 +258,6 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
final Card source = newSA.getHostCard();
|
final Card source = newSA.getHostCard();
|
||||||
if (newSA.isSpell() && !source.isCopiedSpell()) {
|
if (newSA.isSpell() && !source.isCopiedSpell()) {
|
||||||
source.setCastSA(newSA);
|
|
||||||
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
|
||||||
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()) {
|
if (newSA.getApi() == ApiType.Charm && !newSA.isWrapper()) {
|
||||||
@@ -290,9 +278,6 @@ public class ComputerUtil {
|
|||||||
if (ComputerUtilCost.canPayCost(sa, ai)) {
|
if (ComputerUtilCost.canPayCost(sa, ai)) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
if (sa.isSpell() && !source.isCopiedSpell()) {
|
if (sa.isSpell() && !source.isCopiedSpell()) {
|
||||||
source.setCastSA(sa);
|
|
||||||
sa.setLastStateBattlefield(game.getLastStateBattlefield());
|
|
||||||
sa.setLastStateGraveyard(game.getLastStateGraveyard());
|
|
||||||
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1367,7 +1352,7 @@ public class ComputerUtil {
|
|||||||
if (valid.contains("Creature.YouCtrl")
|
if (valid.contains("Creature.YouCtrl")
|
||||||
|| valid.contains("Other+YouCtrl") ) {
|
|| valid.contains("Other+YouCtrl") ) {
|
||||||
|
|
||||||
final SpellAbility sa = t.getTriggeredSA();
|
final SpellAbility sa = t.getOverridingAbility();
|
||||||
if (sa != null && sa.getApi() == ApiType.Pump && sa.hasParam("KW")
|
if (sa != null && sa.getApi() == ApiType.Pump && sa.hasParam("KW")
|
||||||
&& sa.getParam("KW").contains("Haste")) {
|
&& sa.getParam("KW").contains("Haste")) {
|
||||||
return true;
|
return true;
|
||||||
@@ -2216,10 +2201,13 @@ public class ComputerUtil {
|
|||||||
return getCardsToDiscardFromOpponent(aiChooser, p, sa, validCards, min, max);
|
return getCardsToDiscardFromOpponent(aiChooser, p, sa, validCards, min, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String chooseSomeType(Player ai, String kindOfType, String logic, List<String> invalidTypes) {
|
public static String chooseSomeType(Player ai, String kindOfType, String logic, Collection<String> validTypes, List<String> invalidTypes) {
|
||||||
if (invalidTypes == null) {
|
if (invalidTypes == null) {
|
||||||
invalidTypes = ImmutableList.of();
|
invalidTypes = ImmutableList.of();
|
||||||
}
|
}
|
||||||
|
if (validTypes == null) {
|
||||||
|
validTypes = ImmutableList.of();
|
||||||
|
}
|
||||||
|
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
String chosen = "";
|
String chosen = "";
|
||||||
@@ -2243,7 +2231,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(chosen)) {
|
if (StringUtils.isEmpty(chosen)) {
|
||||||
chosen = "Creature";
|
chosen = validTypes.isEmpty() ? "Creature" : Aggregates.random(validTypes);
|
||||||
}
|
}
|
||||||
} else if (kindOfType.equals("Creature")) {
|
} else if (kindOfType.equals("Creature")) {
|
||||||
if (logic != null) {
|
if (logic != null) {
|
||||||
@@ -2338,13 +2326,15 @@ public class ComputerUtil {
|
|||||||
return chosen;
|
return chosen;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Object vote(Player ai, List<Object> options, SpellAbility sa, Multimap<Object, Player> votes) {
|
public static Object vote(Player ai, List<Object> options, SpellAbility sa, Multimap<Object, Player> votes, Player forPlayer) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Player controller = source.getController();
|
final Player controller = source.getController();
|
||||||
final Game game = controller.getGame();
|
final Game game = controller.getGame();
|
||||||
|
|
||||||
boolean opponent = controller.isOpponentOf(ai);
|
boolean opponent = controller.isOpponentOf(ai);
|
||||||
|
|
||||||
|
final CounterType p1p1Type = CounterType.get(CounterEnumType.P1P1);
|
||||||
|
|
||||||
if (!sa.hasParam("AILogic")) {
|
if (!sa.hasParam("AILogic")) {
|
||||||
return Aggregates.random(options);
|
return Aggregates.random(options);
|
||||||
}
|
}
|
||||||
@@ -2398,7 +2388,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// is it can't receive counters, choose +1/+1 ones
|
// is it can't receive counters, choose +1/+1 ones
|
||||||
if (!source.canReceiveCounters(CounterType.P1P1)) {
|
if (!source.canReceiveCounters(p1p1Type)) {
|
||||||
return opponent ? "Feather" : "Quill";
|
return opponent ? "Feather" : "Quill";
|
||||||
}
|
}
|
||||||
// if source is not on the battlefield anymore, choose +1/+1
|
// if source is not on the battlefield anymore, choose +1/+1
|
||||||
@@ -2430,7 +2420,7 @@ public class ComputerUtil {
|
|||||||
Card token = TokenAi.spawnToken(controller, saToken);
|
Card token = TokenAi.spawnToken(controller, saToken);
|
||||||
|
|
||||||
// is it can't receive counters, choose +1/+1 ones
|
// is it can't receive counters, choose +1/+1 ones
|
||||||
if (!source.canReceiveCounters(CounterType.P1P1)) {
|
if (!source.canReceiveCounters(p1p1Type)) {
|
||||||
return opponent ? "Strength" : "Numbers";
|
return opponent ? "Strength" : "Numbers";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2440,7 +2430,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// token would not survive
|
// token would not survive
|
||||||
if (token == null) {
|
if (token == null || !token.isCreature() || token.getNetToughness() < 1) {
|
||||||
return opponent ? "Numbers" : "Strength";
|
return opponent ? "Numbers" : "Strength";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2453,11 +2443,11 @@ public class ComputerUtil {
|
|||||||
Card sourceNumbers = CardUtil.getLKICopy(source);
|
Card sourceNumbers = CardUtil.getLKICopy(source);
|
||||||
Card sourceStrength = CardUtil.getLKICopy(source);
|
Card sourceStrength = CardUtil.getLKICopy(source);
|
||||||
|
|
||||||
sourceNumbers.setCounters(CounterType.P1P1, sourceNumbers.getCounters(CounterType.P1P1) + numStrength);
|
sourceNumbers.setCounters(p1p1Type, sourceNumbers.getCounters(p1p1Type) + numStrength);
|
||||||
sourceNumbers.setZone(source.getZone());
|
sourceNumbers.setZone(source.getZone());
|
||||||
|
|
||||||
sourceStrength.setCounters(CounterType.P1P1,
|
sourceStrength.setCounters(p1p1Type,
|
||||||
sourceStrength.getCounters(CounterType.P1P1) + numStrength + 1);
|
sourceStrength.getCounters(p1p1Type) + numStrength + 1);
|
||||||
sourceStrength.setZone(source.getZone());
|
sourceStrength.setZone(source.getZone());
|
||||||
|
|
||||||
int scoreStrength = ComputerUtilCard.evaluateCreature(sourceStrength) + tokenScore * numNumbers;
|
int scoreStrength = ComputerUtilCard.evaluateCreature(sourceStrength) + tokenScore * numNumbers;
|
||||||
@@ -2479,7 +2469,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// is it can't receive counters, choose +1/+1 ones
|
// is it can't receive counters, choose +1/+1 ones
|
||||||
if (!source.canReceiveCounters(CounterType.P1P1)) {
|
if (!source.canReceiveCounters(p1p1Type)) {
|
||||||
return opponent ? "Sprout" : "Harvest";
|
return opponent ? "Sprout" : "Harvest";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2771,27 +2761,27 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isNegativeCounter(CounterType type, Card c) {
|
public static boolean isNegativeCounter(CounterType type, Card c) {
|
||||||
return type == CounterType.AGE || type == CounterType.BRIBERY || type == CounterType.DOOM
|
return type.is(CounterEnumType.AGE) || type.is(CounterEnumType.BRIBERY) || type.is(CounterEnumType.DOOM)
|
||||||
|| type == CounterType.M1M1 || type == CounterType.M0M2 || type == CounterType.M0M1
|
|| type.is(CounterEnumType.M1M1) || type.is(CounterEnumType.M0M2) || type.is(CounterEnumType.M0M1)
|
||||||
|| type == CounterType.M1M0 || type == CounterType.M2M1 || type == CounterType.M2M2
|
|| type.is(CounterEnumType.M1M0) || type.is(CounterEnumType.M2M1) || type.is(CounterEnumType.M2M2)
|
||||||
// Blaze only hurts Lands
|
// Blaze only hurts Lands
|
||||||
|| (type == CounterType.BLAZE && c.isLand())
|
|| (type.is(CounterEnumType.BLAZE) && c.isLand())
|
||||||
// Iceberg does use Ice as Storage
|
// Iceberg does use Ice as Storage
|
||||||
|| (type == CounterType.ICE && !"Iceberg".equals(c.getName()))
|
|| (type.is(CounterEnumType.ICE) && !"Iceberg".equals(c.getName()))
|
||||||
// some lands does use Depletion as Storage Counter
|
// some lands does use Depletion as Storage Counter
|
||||||
|| (type == CounterType.DEPLETION && !c.canUntapPhaseController())
|
|| (type.is(CounterEnumType.DEPLETION) && c.hasKeyword("CARDNAME doesn't untap during your untap step."))
|
||||||
// treat Time Counters on suspended Cards as Bad,
|
// treat Time Counters on suspended Cards as Bad,
|
||||||
// and also on Chronozoa
|
// and also on Chronozoa
|
||||||
|| (type == CounterType.TIME && (!c.isInPlay() || "Chronozoa".equals(c.getName())))
|
|| (type.is(CounterEnumType.TIME) && (!c.isInPlay() || "Chronozoa".equals(c.getName())))
|
||||||
|| type == CounterType.GOLD || type == CounterType.MUSIC || type == CounterType.PUPA
|
|| type.is(CounterEnumType.GOLD) || type.is(CounterEnumType.MUSIC) || type.is(CounterEnumType.PUPA)
|
||||||
|| type == CounterType.PARALYZATION || type == CounterType.SHELL || type == CounterType.SLEEP
|
|| type.is(CounterEnumType.PARALYZATION) || type.is(CounterEnumType.SHELL) || type.is(CounterEnumType.SLEEP)
|
||||||
|| type == CounterType.SLUMBER || type == CounterType.SLEIGHT || type == CounterType.WAGE;
|
|| type.is(CounterEnumType.SLUMBER) || type.is(CounterEnumType.SLEIGHT) || type.is(CounterEnumType.WAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this countertypes has no effect
|
// this countertypes has no effect
|
||||||
public static boolean isUselessCounter(CounterType type) {
|
public static boolean isUselessCounter(CounterType type) {
|
||||||
return type == CounterType.AWAKENING || type == CounterType.MANIFESTATION || type == CounterType.PETRIFICATION
|
return type.is(CounterEnumType.AWAKENING) || type.is(CounterEnumType.MANIFESTATION) || type.is(CounterEnumType.PETRIFICATION)
|
||||||
|| type == CounterType.TRAINING;
|
|| type.is(CounterEnumType.TRAINING);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Player evaluateBoardPosition(final List<Player> listToEvaluate) {
|
public static Player evaluateBoardPosition(final List<Player> listToEvaluate) {
|
||||||
|
|||||||
@@ -82,6 +82,10 @@ public class ComputerUtilAbility {
|
|||||||
final List<SpellAbility> spellAbilities = Lists.newArrayList();
|
final List<SpellAbility> spellAbilities = Lists.newArrayList();
|
||||||
for (final Card c : l) {
|
for (final Card c : l) {
|
||||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||||
|
// Spells of permanents can't be activated on the battlefield
|
||||||
|
if (c.isPermanent() && sa.isSpell() && c.isInZone(ZoneType.Battlefield)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
spellAbilities.add(sa);
|
spellAbilities.add(sa);
|
||||||
}
|
}
|
||||||
if (c.isFaceDown() && c.isInZone(ZoneType.Exile) && !c.mayPlay(player).isEmpty()) {
|
if (c.isFaceDown() && c.isInZone(ZoneType.Exile) && !c.mayPlay(player).isEmpty()) {
|
||||||
@@ -109,9 +113,7 @@ public class ComputerUtilAbility {
|
|||||||
List<SpellAbility> priorityAltSa = Lists.newArrayList();
|
List<SpellAbility> priorityAltSa = Lists.newArrayList();
|
||||||
List<SpellAbility> otherAltSa = Lists.newArrayList();
|
List<SpellAbility> otherAltSa = Lists.newArrayList();
|
||||||
for (SpellAbility altSa : saAltCosts) {
|
for (SpellAbility altSa : saAltCosts) {
|
||||||
if (altSa.getPayCosts() == null || sa.getPayCosts() == null) {
|
if (sa.getPayCosts().isOnlyManaCost()
|
||||||
otherAltSa.add(altSa);
|
|
||||||
} else if (sa.getPayCosts().isOnlyManaCost()
|
|
||||||
&& altSa.getPayCosts().isOnlyManaCost() && sa.getPayCosts().getTotalMana().compareTo(altSa.getPayCosts().getTotalMana()) == 1) {
|
&& altSa.getPayCosts().isOnlyManaCost() && sa.getPayCosts().getTotalMana().compareTo(altSa.getPayCosts().getTotalMana()) == 1) {
|
||||||
// the alternative cost is strictly cheaper, so why not? (e.g. Omniscience etc.)
|
// the alternative cost is strictly cheaper, so why not? (e.g. Omniscience etc.)
|
||||||
priorityAltSa.add(altSa);
|
priorityAltSa.add(altSa);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ 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.card.CardType;
|
import forge.card.CardType;
|
||||||
import forge.card.ColorSet;
|
import forge.card.ColorSet;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
@@ -172,21 +171,22 @@ public class ComputerUtilCard {
|
|||||||
|
|
||||||
// if no non-basic lands, target the least represented basic land type
|
// if no non-basic lands, target the least represented basic land type
|
||||||
String sminBL = "";
|
String sminBL = "";
|
||||||
int iminBL = 20000; // hopefully no one will ever have more than 20000
|
int iminBL = Integer.MAX_VALUE;
|
||||||
// lands of one type....
|
|
||||||
int n = 0;
|
int n = 0;
|
||||||
for (String name : MagicColor.Constant.BASIC_LANDS) {
|
for (String name : MagicColor.Constant.BASIC_LANDS) {
|
||||||
n = CardLists.getType(land, name).size();
|
n = CardLists.getType(land, name).size();
|
||||||
if ((n < iminBL) && (n > 0)) {
|
if (n < iminBL && n > 0) {
|
||||||
// if two or more are tied, only the
|
|
||||||
// first
|
|
||||||
// one checked will be used
|
|
||||||
iminBL = n;
|
iminBL = n;
|
||||||
sminBL = name;
|
sminBL = name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (iminBL == 20000) {
|
if (iminBL == Integer.MAX_VALUE) {
|
||||||
return null; // no basic land was a minimum
|
// All basic lands have no basic land type. Just return something
|
||||||
|
Iterator<Card> untapped = Iterables.filter(land, CardPredicates.Presets.UNTAPPED).iterator();
|
||||||
|
if (untapped.hasNext()) {
|
||||||
|
return untapped.next();
|
||||||
|
}
|
||||||
|
return land.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Card> bLand = CardLists.getType(land, sminBL);
|
final List<Card> bLand = CardLists.getType(land, sminBL);
|
||||||
@@ -195,7 +195,6 @@ public class ComputerUtilCard {
|
|||||||
return ut;
|
return ut;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return Aggregates.random(bLand); // random tapped land of least represented type
|
return Aggregates.random(bLand); // random tapped land of least represented type
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -643,7 +642,7 @@ public class ComputerUtilCard {
|
|||||||
return getMostProminentType(list, CardType.getAllCreatureTypes());
|
return getMostProminentType(list, CardType.getAllCreatureTypes());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getMostProminentType(final CardCollectionView list, final List<String> valid) {
|
public static String getMostProminentType(final CardCollectionView list, final Collection<String> valid) {
|
||||||
if (list.size() == 0) {
|
if (list.size() == 0) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -964,6 +963,22 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
chosen.add(chosenColor);
|
chosen.add(chosenColor);
|
||||||
}
|
}
|
||||||
|
else if (logic.equals("HighestDevotionToColor")) {
|
||||||
|
int curDevotion = 0;
|
||||||
|
String chosenColor = MagicColor.Constant.WHITE;
|
||||||
|
CardCollectionView hand = ai.getCardsIn(ZoneType.Hand);
|
||||||
|
for(byte c : MagicColor.WUBRG) {
|
||||||
|
String devotionCode = "Count$Devotion." + MagicColor.toLongString(c);
|
||||||
|
|
||||||
|
int devotion = CardFactoryUtil.xCount(sa.getHostCard(), devotionCode);
|
||||||
|
if (devotion > curDevotion && !CardLists.filter(hand, CardPredicates.isColor(c)).isEmpty()) {
|
||||||
|
curDevotion = devotion;
|
||||||
|
chosenColor = MagicColor.toLongString(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chosen.add(chosenColor);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if (chosen.isEmpty()) {
|
if (chosen.isEmpty()) {
|
||||||
chosen.add(MagicColor.Constant.GREEN);
|
chosen.add(MagicColor.Constant.GREEN);
|
||||||
@@ -1407,8 +1422,8 @@ public class ComputerUtilCard {
|
|||||||
if (combat.isAttacking(c) && opp.getLife() > 0) {
|
if (combat.isAttacking(c) && opp.getLife() > 0) {
|
||||||
int dmg = ComputerUtilCombat.damageIfUnblocked(c, opp, combat, true);
|
int dmg = ComputerUtilCombat.damageIfUnblocked(c, opp, combat, true);
|
||||||
int pumpedDmg = ComputerUtilCombat.damageIfUnblocked(pumped, opp, pumpedCombat, true);
|
int pumpedDmg = ComputerUtilCombat.damageIfUnblocked(pumped, opp, pumpedCombat, true);
|
||||||
int poisonOrig = opp.canReceiveCounters(CounterType.POISON) ? ComputerUtilCombat.poisonIfUnblocked(c, ai) : 0;
|
int poisonOrig = opp.canReceiveCounters(CounterEnumType.POISON) ? ComputerUtilCombat.poisonIfUnblocked(c, ai) : 0;
|
||||||
int poisonPumped = opp.canReceiveCounters(CounterType.POISON) ? ComputerUtilCombat.poisonIfUnblocked(pumped, ai) : 0;
|
int poisonPumped = opp.canReceiveCounters(CounterEnumType.POISON) ? ComputerUtilCombat.poisonIfUnblocked(pumped, ai) : 0;
|
||||||
|
|
||||||
// predict Infect
|
// predict Infect
|
||||||
if (pumpedDmg == 0 && c.hasKeyword(Keyword.INFECT)) {
|
if (pumpedDmg == 0 && c.hasKeyword(Keyword.INFECT)) {
|
||||||
@@ -1431,7 +1446,8 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
if (pumpedDmg > dmg) {
|
if (pumpedDmg > dmg) {
|
||||||
if ((!c.hasKeyword(Keyword.INFECT) && pumpedDmg >= opp.getLife())
|
if ((!c.hasKeyword(Keyword.INFECT) && pumpedDmg >= opp.getLife())
|
||||||
|| (c.hasKeyword(Keyword.INFECT) && opp.canReceiveCounters(CounterType.POISON) && pumpedDmg >= opp.getPoisonCounters())) {
|
|| (c.hasKeyword(Keyword.INFECT) && opp.canReceiveCounters(CounterEnumType.POISON) && pumpedDmg >= opp.getPoisonCounters())
|
||||||
|
|| ("PumpForTrample".equals(sa.getParam("AILogic")))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1458,7 +1474,7 @@ public class ComputerUtilCard {
|
|||||||
if (totalPowerUnblocked >= opp.getLife()) {
|
if (totalPowerUnblocked >= opp.getLife()) {
|
||||||
return true;
|
return true;
|
||||||
} else if (totalPowerUnblocked > dmg && sa.getHostCard() != null && sa.getHostCard().isInPlay()) {
|
} else if (totalPowerUnblocked > dmg && sa.getHostCard() != null && sa.getHostCard().isInPlay()) {
|
||||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasNoManaCost()) {
|
if (sa.getPayCosts().hasNoManaCost()) {
|
||||||
return true; // always activate abilities which cost no mana and which can increase unblocked damage
|
return true; // always activate abilities which cost no mana and which can increase unblocked damage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1742,17 +1758,17 @@ public class ComputerUtilCard {
|
|||||||
if (!c.isCreature()) {
|
if (!c.isCreature()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (c.hasKeyword("CARDNAME can't attack or block.") || (!c.canUntapPhaseController() && c.isTapped()) || (c.getOwner() == ai && ai.getOpponents().contains(c.getController()))) {
|
if (c.hasKeyword("CARDNAME can't attack or block.") || (c.hasKeyword("CARDNAME doesn't untap during your untap step.") && c.isTapped()) || (c.getOwner() == ai && ai.getOpponents().contains(c.getController()))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasActiveUndyingOrPersist(final Card c) {
|
public static boolean hasActiveUndyingOrPersist(final Card c) {
|
||||||
if (c.hasKeyword(Keyword.UNDYING) && c.getCounters(CounterType.P1P1) == 0) {
|
if (c.hasKeyword(Keyword.UNDYING) && c.getCounters(CounterEnumType.P1P1) == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (c.hasKeyword(Keyword.PERSIST) && c.getCounters(CounterType.M1M1) == 0) {
|
if (c.hasKeyword(Keyword.PERSIST) && c.getCounters(CounterEnumType.M1M1) == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -1769,10 +1785,6 @@ public class ComputerUtilCard {
|
|||||||
|
|
||||||
for (Card c : otb) {
|
for (Card c : otb) {
|
||||||
for (SpellAbility sa : c.getSpellAbilities()) {
|
for (SpellAbility sa : c.getSpellAbilities()) {
|
||||||
if (sa.getPayCosts() == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
CostPayEnergy energyCost = sa.getPayCosts().getCostEnergy();
|
CostPayEnergy energyCost = sa.getPayCosts().getCostEnergy();
|
||||||
if (energyCost != null) {
|
if (energyCost != null) {
|
||||||
int amount = energyCost.convertAmount();
|
int amount = energyCost.convertAmount();
|
||||||
@@ -1896,21 +1908,12 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
if (card.getSVar(needsToPlayVarName).length() > 0) {
|
if (card.getSVar(needsToPlayVarName).length() > 0) {
|
||||||
final String needsToPlay = card.getSVar(needsToPlayVarName);
|
final String needsToPlay = card.getSVar(needsToPlayVarName);
|
||||||
int x = 0;
|
|
||||||
int y = 0;
|
|
||||||
String sVar = needsToPlay.split(" ")[0];
|
String sVar = needsToPlay.split(" ")[0];
|
||||||
String comparator = needsToPlay.split(" ")[1];
|
String comparator = needsToPlay.split(" ")[1];
|
||||||
String compareTo = comparator.substring(2);
|
String compareTo = comparator.substring(2);
|
||||||
try {
|
int x = AbilityUtils.calculateAmount(card, sVar, sa);
|
||||||
x = Integer.parseInt(sVar);
|
int y = AbilityUtils.calculateAmount(card, compareTo, sa);
|
||||||
} 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)) {
|
if (!Expressions.compare(x, comparator, y)) {
|
||||||
return AiPlayDecision.NeedsToPlayCriteriaNotMet;
|
return AiPlayDecision.NeedsToPlayCriteriaNotMet;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ public class ComputerUtilCombat {
|
|||||||
public static int resultingPoison(final Player ai, final Combat combat) {
|
public static int resultingPoison(final Player ai, final Combat combat) {
|
||||||
|
|
||||||
// ai can't get poision counters, so the value can't change
|
// ai can't get poision counters, so the value can't change
|
||||||
if (!ai.canReceiveCounters(CounterType.POISON)) {
|
if (!ai.canReceiveCounters(CounterEnumType.POISON)) {
|
||||||
return ai.getPoisonCounters();
|
return ai.getPoisonCounters();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -931,7 +931,7 @@ public class ComputerUtilCombat {
|
|||||||
if (dealsFirstStrikeDamage(attacker, withoutAbilities, null)
|
if (dealsFirstStrikeDamage(attacker, withoutAbilities, null)
|
||||||
&& (attacker.hasKeyword(Keyword.WITHER) || attacker.hasKeyword(Keyword.INFECT))
|
&& (attacker.hasKeyword(Keyword.WITHER) || attacker.hasKeyword(Keyword.INFECT))
|
||||||
&& !dealsFirstStrikeDamage(blocker, withoutAbilities, null)
|
&& !dealsFirstStrikeDamage(blocker, withoutAbilities, null)
|
||||||
&& !blocker.canReceiveCounters(CounterType.M1M1)) {
|
&& !blocker.canReceiveCounters(CounterEnumType.M1M1)) {
|
||||||
power -= attacker.getNetCombatDamage();
|
power -= attacker.getNetCombatDamage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -973,62 +973,45 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
theTriggers.addAll(attacker.getTriggers());
|
theTriggers.addAll(attacker.getTriggers());
|
||||||
for (final Trigger trigger : theTriggers) {
|
for (final Trigger trigger : theTriggers) {
|
||||||
final Map<String, String> trigParams = trigger.getMapParams();
|
|
||||||
final Card source = trigger.getHostCard();
|
final Card source = trigger.getHostCard();
|
||||||
|
|
||||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)) {
|
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> abilityParams = null;
|
SpellAbility sa = trigger.ensureAbility();
|
||||||
if (trigger.getOverridingAbility() != null) {
|
if (sa == null) {
|
||||||
abilityParams = trigger.getOverridingAbility().getMapParams();
|
|
||||||
} else if (trigParams.containsKey("Execute")) {
|
|
||||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
|
||||||
abilityParams = AbilityFactory.getMapParams(ability);
|
|
||||||
} else {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abilityParams.containsKey("AB") && !abilityParams.get("AB").equals("Pump")) {
|
if (!ApiType.Pump.equals(sa.getApi())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (abilityParams.containsKey("DB") && !abilityParams.get("DB").equals("Pump")) {
|
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) {
|
|
||||||
continue; // targeted pumping not supported
|
if (!sa.hasParam("NumAtt")) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
final List<Card> list = AbilityUtils.getDefinedCards(source, abilityParams.get("Defined"), null);
|
|
||||||
if (abilityParams.containsKey("Defined") && abilityParams.get("Defined").equals("TriggeredBlocker")) {
|
String defined = sa.getParam("Defined");
|
||||||
|
final List<Card> list = AbilityUtils.getDefinedCards(source, defined, sa);
|
||||||
|
if ("TriggeredBlocker".equals(defined)) {
|
||||||
list.add(blocker);
|
list.add(blocker);
|
||||||
}
|
}
|
||||||
if (list.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!list.contains(blocker)) {
|
if (!list.contains(blocker)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!abilityParams.containsKey("NumAtt")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String att = abilityParams.get("NumAtt");
|
power += AbilityUtils.calculateAmount(source, sa.getParam("NumAtt"), sa, true);
|
||||||
if (att.startsWith("+")) {
|
|
||||||
att = att.substring(1);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
power += Integer.parseInt(att);
|
|
||||||
} catch (final NumberFormatException nfe) {
|
|
||||||
// can't parse the number (X for example)
|
|
||||||
power += 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (withoutAbilities) {
|
if (withoutAbilities) {
|
||||||
return power;
|
return power;
|
||||||
}
|
}
|
||||||
for (SpellAbility ability : blocker.getAllSpellAbilities()) {
|
for (SpellAbility ability : blocker.getAllSpellAbilities()) {
|
||||||
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
if (!(ability instanceof AbilityActivated)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ability.hasParam("ActivationPhases") || ability.hasParam("SorcerySpeed") || ability.hasParam("ActivationZone")) {
|
if (ability.hasParam("ActivationPhases") || ability.hasParam("SorcerySpeed") || ability.hasParam("ActivationZone")) {
|
||||||
@@ -1058,7 +1041,7 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ability.hasParam("Adapt") && blocker.getCounters(CounterType.P1P1) > 0) {
|
if (ability.hasParam("Adapt") && blocker.getCounters(CounterEnumType.P1P1) > 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1108,102 +1091,61 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
theTriggers.addAll(attacker.getTriggers());
|
theTriggers.addAll(attacker.getTriggers());
|
||||||
for (final Trigger trigger : theTriggers) {
|
for (final Trigger trigger : theTriggers) {
|
||||||
final Map<String, String> trigParams = trigger.getMapParams();
|
|
||||||
final Card source = trigger.getHostCard();
|
final Card source = trigger.getHostCard();
|
||||||
|
|
||||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)) {
|
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> abilityParams = null;
|
SpellAbility sa = trigger.ensureAbility();
|
||||||
if (trigger.getOverridingAbility() != null) {
|
if (sa == null) {
|
||||||
abilityParams = trigger.getOverridingAbility().getMapParams();
|
|
||||||
} else if (trigParams.containsKey("Execute")) {
|
|
||||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
|
||||||
abilityParams = AbilityFactory.getMapParams(ability);
|
|
||||||
} else {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
String abType = "";
|
|
||||||
if (abilityParams.containsKey("AB")) {
|
|
||||||
abType = abilityParams.get("AB");
|
|
||||||
} else if (abilityParams.containsKey("DB")) {
|
|
||||||
abType = abilityParams.get("DB");
|
|
||||||
}
|
|
||||||
|
|
||||||
// DealDamage triggers
|
// DealDamage triggers
|
||||||
if (abType.equals("DealDamage")) {
|
if (ApiType.DealDamage.equals(sa.getApi())) {
|
||||||
if (!abilityParams.containsKey("Defined") || !abilityParams.get("Defined").equals("TriggeredBlocker")) {
|
if (!"TriggeredBlocker".equals(sa.getParam("Defined"))) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int damage = 0;
|
|
||||||
try {
|
|
||||||
damage = Integer.parseInt(abilityParams.get("NumDmg"));
|
|
||||||
} catch (final NumberFormatException nfe) {
|
|
||||||
// can't parse the number (X for example)
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
int damage = AbilityUtils.calculateAmount(source, sa.getParam("NumDmg"), sa);
|
||||||
toughness -= predictDamageTo(blocker, damage, 0, source, false);
|
toughness -= predictDamageTo(blocker, damage, 0, source, false);
|
||||||
continue;
|
} else
|
||||||
}
|
|
||||||
|
|
||||||
// -1/-1 PutCounter triggers
|
// -1/-1 PutCounter triggers
|
||||||
if (abType.equals("PutCounter")) {
|
if (ApiType.PutCounter.equals(sa.getApi())) {
|
||||||
if (!abilityParams.containsKey("Defined") || !abilityParams.get("Defined").equals("TriggeredBlocker")) {
|
if (!"TriggeredBlocker".equals(sa.getParam("Defined"))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!abilityParams.containsKey("CounterType") || !abilityParams.get("CounterType").equals("M1M1")) {
|
if (!"M1M1".equals(sa.getParam("CounterType"))) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int num = 0;
|
|
||||||
try {
|
|
||||||
num = Integer.parseInt(abilityParams.get("CounterNum"));
|
|
||||||
} catch (final NumberFormatException nfe) {
|
|
||||||
// can't parse the number (X for example)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
toughness -= num;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
toughness -= AbilityUtils.calculateAmount(source, sa.getParam("CounterNum"), sa);
|
||||||
|
} else
|
||||||
|
|
||||||
// Pump triggers
|
// Pump triggers
|
||||||
if (!abType.equals("Pump")) {
|
if (ApiType.Pump.equals(sa.getApi())) {
|
||||||
continue;
|
if (sa.usesTargeting()) {
|
||||||
}
|
|
||||||
if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) {
|
|
||||||
continue; // targeted pumping not supported
|
continue; // targeted pumping not supported
|
||||||
}
|
}
|
||||||
final List<Card> list = AbilityUtils.getDefinedCards(source, abilityParams.get("Defined"), null);
|
final List<Card> list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), null);
|
||||||
if (abilityParams.containsKey("Defined") && abilityParams.get("Defined").equals("TriggeredBlocker")) {
|
if ("TriggeredBlocker".equals(sa.getParam("Defined"))) {
|
||||||
list.add(blocker);
|
list.add(blocker);
|
||||||
}
|
}
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty() || !list.contains(blocker)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!list.contains(blocker)) {
|
if (!sa.hasParam("NumDef")) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!abilityParams.containsKey("NumDef")) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
String def = abilityParams.get("NumDef");
|
toughness += AbilityUtils.calculateAmount(source, sa.getParam("NumDef"), sa, true);
|
||||||
if (def.startsWith("+")) {
|
|
||||||
def = def.substring(1);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
toughness += Integer.parseInt(def);
|
|
||||||
} catch (final NumberFormatException nfe) {
|
|
||||||
// can't parse the number (X for example)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (withoutAbilities) {
|
if (withoutAbilities) {
|
||||||
return toughness;
|
return toughness;
|
||||||
}
|
}
|
||||||
for (SpellAbility ability : blocker.getAllSpellAbilities()) {
|
for (SpellAbility ability : blocker.getAllSpellAbilities()) {
|
||||||
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
if (!(ability instanceof AbilityActivated)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1234,7 +1176,7 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ability.hasParam("Adapt") && blocker.getCounters(CounterType.P1P1) > 0) {
|
if (ability.hasParam("Adapt") && blocker.getCounters(CounterEnumType.P1P1) > 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1296,7 +1238,7 @@ public class ComputerUtilCombat {
|
|||||||
if (ComputerUtilCombat.dealsFirstStrikeDamage(blocker, withoutAbilities, combat)
|
if (ComputerUtilCombat.dealsFirstStrikeDamage(blocker, withoutAbilities, combat)
|
||||||
&& (blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT))
|
&& (blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT))
|
||||||
&& !ComputerUtilCombat.dealsFirstStrikeDamage(attacker, withoutAbilities, combat)
|
&& !ComputerUtilCombat.dealsFirstStrikeDamage(attacker, withoutAbilities, combat)
|
||||||
&& !attacker.canReceiveCounters(CounterType.M1M1)) {
|
&& !attacker.canReceiveCounters(CounterEnumType.M1M1)) {
|
||||||
power -= blocker.getNetCombatDamage();
|
power -= blocker.getNetCombatDamage();
|
||||||
}
|
}
|
||||||
theTriggers.addAll(blocker.getTriggers());
|
theTriggers.addAll(blocker.getTriggers());
|
||||||
@@ -1426,7 +1368,7 @@ public class ComputerUtilCombat {
|
|||||||
return power;
|
return power;
|
||||||
}
|
}
|
||||||
for (SpellAbility ability : attacker.getAllSpellAbilities()) {
|
for (SpellAbility ability : attacker.getAllSpellAbilities()) {
|
||||||
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
if (!(ability instanceof AbilityActivated)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ability.hasParam("ActivationPhases") || ability.hasParam("SorcerySpeed") || ability.hasParam("ActivationZone")) {
|
if (ability.hasParam("ActivationPhases") || ability.hasParam("SorcerySpeed") || ability.hasParam("ActivationZone")) {
|
||||||
@@ -1456,7 +1398,7 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ability.hasParam("Adapt") && attacker.getCounters(CounterType.P1P1) > 0) {
|
if (ability.hasParam("Adapt") && attacker.getCounters(CounterEnumType.P1P1) > 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1521,135 +1463,121 @@ public class ComputerUtilCombat {
|
|||||||
final CardCollectionView cardList = game.getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView cardList = game.getCardsIn(ZoneType.Battlefield);
|
||||||
for (final Card card : cardList) {
|
for (final Card card : cardList) {
|
||||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||||
final Map<String, String> params = stAb.getMapParams();
|
if (!"Continuous".equals(stAb.getParam("Mode"))) {
|
||||||
if (!params.get("Mode").equals("Continuous")) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("Affected") && params.get("Affected").contains("attacking")) {
|
if (!stAb.hasParam("Affected")) {
|
||||||
final String valid = TextUtil.fastReplace(params.get("Affected"), "attacking", "Creature");
|
continue;
|
||||||
|
}
|
||||||
|
if (!stAb.hasParam("AddToughness")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String affected = stAb.getParam("Affected");
|
||||||
|
String addT = stAb.getParam("AddToughness");
|
||||||
|
if (affected.contains("attacking")) {
|
||||||
|
final String valid = TextUtil.fastReplace(affected, "attacking", "Creature");
|
||||||
if (!attacker.isValid(valid, card.getController(), card, null)) {
|
if (!attacker.isValid(valid, card.getController(), card, null)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("AddToughness")) {
|
toughness += AbilityUtils.calculateAmount(card, addT, stAb, true);
|
||||||
if (params.get("AddToughness").equals("X")) {
|
} else if (affected.contains("untapped")) {
|
||||||
toughness += CardFactoryUtil.xCount(card, card.getSVar("X"));
|
final String valid = TextUtil.fastReplace(affected, "untapped", "Creature");
|
||||||
} else if (params.get("AddToughness").equals("Y")) {
|
|
||||||
toughness += CardFactoryUtil.xCount(card, card.getSVar("Y"));
|
|
||||||
} else {
|
|
||||||
toughness += Integer.valueOf(params.get("AddToughness"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (params.containsKey("Affected") && params.get("Affected").contains("untapped")) {
|
|
||||||
final String valid = TextUtil.fastReplace(params.get("Affected"), "untapped", "Creature");
|
|
||||||
if (!attacker.isValid(valid, card.getController(), card, null)
|
if (!attacker.isValid(valid, card.getController(), card, null)
|
||||||
|| attacker.hasKeyword(Keyword.VIGILANCE)) {
|
|| attacker.hasKeyword(Keyword.VIGILANCE)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// remove the bonus, because it will no longer be granted
|
// remove the bonus, because it will no longer be granted
|
||||||
if (params.containsKey("AddToughness")) {
|
toughness -= AbilityUtils.calculateAmount(card, addT, stAb, true);
|
||||||
toughness -= Integer.valueOf(params.get("AddToughness"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final Trigger trigger : theTriggers) {
|
for (final Trigger trigger : theTriggers) {
|
||||||
final Map<String, String> trigParams = trigger.getMapParams();
|
|
||||||
final Card source = trigger.getHostCard();
|
final Card source = trigger.getHostCard();
|
||||||
|
|
||||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, combat)) {
|
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, combat)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> abilityParams = null;
|
SpellAbility sa = trigger.ensureAbility();
|
||||||
if (trigger.getOverridingAbility() != null) {
|
if (sa == null) {
|
||||||
abilityParams = trigger.getOverridingAbility().getMapParams();
|
|
||||||
} else if (trigParams.containsKey("Execute")) {
|
|
||||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
|
||||||
abilityParams = AbilityFactory.getMapParams(ability);
|
|
||||||
} else {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
sa.setActivatingPlayer(source.getController());
|
||||||
|
|
||||||
if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) {
|
if (sa.usesTargeting()) {
|
||||||
continue; // targeted pumping not supported
|
continue; // targeted pumping not supported
|
||||||
}
|
}
|
||||||
|
|
||||||
// DealDamage triggers
|
// DealDamage triggers
|
||||||
if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("DealDamage"))
|
if (ApiType.DealDamage.equals(sa.getApi())) {
|
||||||
|| (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("DealDamage"))) {
|
if ("TriggeredAttacker".equals(sa.getParam("Defined"))) {
|
||||||
if (!abilityParams.containsKey("Defined") || !abilityParams.get("Defined").equals("TriggeredAttacker")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int damage = 0;
|
|
||||||
try {
|
|
||||||
damage = Integer.parseInt(abilityParams.get("NumDmg"));
|
|
||||||
} catch (final NumberFormatException nfe) {
|
|
||||||
// can't parse the number (X for example)
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
int damage = AbilityUtils.calculateAmount(source, sa.getParam("NumDmg"), sa);
|
||||||
|
|
||||||
toughness -= predictDamageTo(attacker, damage, 0, source, false);
|
toughness -= predictDamageTo(attacker, damage, 0, source, false);
|
||||||
continue;
|
continue;
|
||||||
}
|
} else if (ApiType.Pump.equals(sa.getApi())) {
|
||||||
|
|
||||||
// Pump triggers
|
if (sa.hasParam("Cost")) {
|
||||||
if (abilityParams.containsKey("AB") && !abilityParams.get("AB").equals("Pump")
|
|
||||||
&& !abilityParams.get("AB").equals("PumpAll")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (abilityParams.containsKey("DB") && !abilityParams.get("DB").equals("Pump")
|
|
||||||
&& !abilityParams.get("DB").equals("PumpAll")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (abilityParams.containsKey("Cost")) {
|
|
||||||
SpellAbility sa = null;
|
|
||||||
if (trigger.getOverridingAbility() != null) {
|
|
||||||
sa = trigger.getOverridingAbility();
|
|
||||||
} else {
|
|
||||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
|
||||||
sa = AbilityFactory.getAbility(ability, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
sa.setActivatingPlayer(source.getController());
|
|
||||||
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!sa.hasParam("NumDef")) {
|
||||||
List<Card> list = Lists.newArrayList();
|
|
||||||
if (!abilityParams.containsKey("ValidCards")) {
|
|
||||||
list = AbilityUtils.getDefinedCards(source, abilityParams.get("Defined"), null);
|
|
||||||
}
|
|
||||||
if (abilityParams.containsKey("Defined") && abilityParams.get("Defined").equals("TriggeredAttacker")) {
|
|
||||||
list.add(attacker);
|
|
||||||
}
|
|
||||||
if (abilityParams.containsKey("ValidCards")) {
|
|
||||||
if (attacker.isValid(abilityParams.get("ValidCards").split(","), source.getController(), source, null)
|
|
||||||
|| attacker.isValid(abilityParams.get("ValidCards").replace("attacking+", "").split(","),
|
|
||||||
source.getController(), source, null)) {
|
|
||||||
list.add(attacker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (list.isEmpty()) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
CardCollection list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
|
if ("TriggeredAttacker".equals(sa.getParam("Defined"))) {
|
||||||
|
list.add(attacker);
|
||||||
|
}
|
||||||
if (!list.contains(attacker)) {
|
if (!list.contains(attacker)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!abilityParams.containsKey("NumDef")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String def = abilityParams.get("NumDef");
|
String def = sa.getParam("NumDef");
|
||||||
if (def.startsWith("+")) {
|
if (def.startsWith("+")) {
|
||||||
def = def.substring(1);
|
def = def.substring(1);
|
||||||
}
|
}
|
||||||
if (def.matches("[0-9][0-9]?") || def.matches("-" + "[0-9][0-9]?")) {
|
if (def.matches("[0-9][0-9]?") || def.matches("-" + "[0-9][0-9]?")) {
|
||||||
toughness += Integer.parseInt(def);
|
toughness += Integer.parseInt(def);
|
||||||
} else {
|
} else {
|
||||||
String bonus = source.getSVar(def);
|
String bonus = AbilityUtils.getSVar(sa, def);
|
||||||
|
if (bonus.contains("TriggerCount$NumBlockers")) {
|
||||||
|
bonus = TextUtil.fastReplace(bonus, "TriggerCount$NumBlockers", "Number$1");
|
||||||
|
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
||||||
|
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
|
||||||
|
}
|
||||||
|
toughness += CardFactoryUtil.xCount(source, bonus);
|
||||||
|
}
|
||||||
|
} else if (ApiType.PumpAll.equals(sa.getApi())) {
|
||||||
|
|
||||||
|
if (sa.hasParam("Cost")) {
|
||||||
|
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sa.hasParam("ValidCards")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!sa.hasParam("NumDef")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!attacker.isValid(sa.getParam("ValidCards").replace("attacking+", "").split(","), source.getController(), source, sa)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String def = sa.getParam("NumDef");
|
||||||
|
if (def.startsWith("+")) {
|
||||||
|
def = def.substring(1);
|
||||||
|
}
|
||||||
|
if (def.matches("[0-9][0-9]?") || def.matches("-" + "[0-9][0-9]?")) {
|
||||||
|
toughness += Integer.parseInt(def);
|
||||||
|
} else {
|
||||||
|
String bonus = AbilityUtils.getSVar(sa, def);
|
||||||
if (bonus.contains("TriggerCount$NumBlockers")) {
|
if (bonus.contains("TriggerCount$NumBlockers")) {
|
||||||
bonus = TextUtil.fastReplace(bonus, "TriggerCount$NumBlockers", "Number$1");
|
bonus = TextUtil.fastReplace(bonus, "TriggerCount$NumBlockers", "Number$1");
|
||||||
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
||||||
@@ -1658,11 +1586,12 @@ public class ComputerUtilCombat {
|
|||||||
toughness += CardFactoryUtil.xCount(source, bonus);
|
toughness += CardFactoryUtil.xCount(source, bonus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (withoutAbilities) {
|
if (withoutAbilities) {
|
||||||
return toughness;
|
return toughness;
|
||||||
}
|
}
|
||||||
for (SpellAbility ability : attacker.getAllSpellAbilities()) {
|
for (SpellAbility ability : attacker.getAllSpellAbilities()) {
|
||||||
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
if (!(ability instanceof AbilityActivated)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1672,18 +1601,19 @@ public class ComputerUtilCombat {
|
|||||||
if (ability.usesTargeting() && !ability.canTarget(attacker)) {
|
if (ability.usesTargeting() && !ability.canTarget(attacker)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (ability.getPayCosts().hasTapCost() && !attacker.hasKeyword(Keyword.VIGILANCE)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!ComputerUtilCost.canPayCost(ability, attacker.getController())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (ability.getApi() == ApiType.Pump) {
|
if (ability.getApi() == ApiType.Pump) {
|
||||||
if (!ability.hasParam("NumDef")) {
|
if (!ability.hasParam("NumDef")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) {
|
toughness += AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability, true);
|
||||||
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
|
|
||||||
if (tBonus > 0) {
|
|
||||||
toughness += tBonus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1693,18 +1623,16 @@ public class ComputerUtilCombat {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ability.hasParam("Adapt") && attacker.getCounters(CounterType.P1P1) > 0) {
|
if (ability.hasParam("Adapt") && attacker.getCounters(CounterEnumType.P1P1) > 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) {
|
|
||||||
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("CounterNum"), ability);
|
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("CounterNum"), ability);
|
||||||
if (tBonus > 0) {
|
if (tBonus > 0) {
|
||||||
toughness += tBonus;
|
toughness += tBonus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return toughness;
|
return toughness;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1848,10 +1776,10 @@ public class ComputerUtilCombat {
|
|||||||
|
|
||||||
if (((attacker.hasKeyword(Keyword.INDESTRUCTIBLE) || (ComputerUtil.canRegenerate(ai, attacker) && !withoutAbilities))
|
if (((attacker.hasKeyword(Keyword.INDESTRUCTIBLE) || (ComputerUtil.canRegenerate(ai, attacker) && !withoutAbilities))
|
||||||
&& !(blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT)))
|
&& !(blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT)))
|
||||||
|| (attacker.hasKeyword(Keyword.PERSIST) && !attacker.canReceiveCounters(CounterType.M1M1) && (attacker
|
|| (attacker.hasKeyword(Keyword.PERSIST) && !attacker.canReceiveCounters(CounterEnumType.M1M1) && (attacker
|
||||||
.getCounters(CounterType.M1M1) == 0))
|
.getCounters(CounterEnumType.M1M1) == 0))
|
||||||
|| (attacker.hasKeyword(Keyword.UNDYING) && !attacker.canReceiveCounters(CounterType.P1P1) && (attacker
|
|| (attacker.hasKeyword(Keyword.UNDYING) && !attacker.canReceiveCounters(CounterEnumType.P1P1) && (attacker
|
||||||
.getCounters(CounterType.P1P1) == 0))) {
|
.getCounters(CounterEnumType.P1P1) == 0))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2080,10 +2008,10 @@ public class ComputerUtilCombat {
|
|||||||
|
|
||||||
if (((blocker.hasKeyword(Keyword.INDESTRUCTIBLE) || (ComputerUtil.canRegenerate(ai, blocker) && !withoutAbilities)) && !(attacker
|
if (((blocker.hasKeyword(Keyword.INDESTRUCTIBLE) || (ComputerUtil.canRegenerate(ai, blocker) && !withoutAbilities)) && !(attacker
|
||||||
.hasKeyword(Keyword.WITHER) || attacker.hasKeyword(Keyword.INFECT)))
|
.hasKeyword(Keyword.WITHER) || attacker.hasKeyword(Keyword.INFECT)))
|
||||||
|| (blocker.hasKeyword(Keyword.PERSIST) && !blocker.canReceiveCounters(CounterType.M1M1) && (blocker
|
|| (blocker.hasKeyword(Keyword.PERSIST) && !blocker.canReceiveCounters(CounterEnumType.M1M1) && (blocker
|
||||||
.getCounters(CounterType.M1M1) == 0))
|
.getCounters(CounterEnumType.M1M1) == 0))
|
||||||
|| (blocker.hasKeyword(Keyword.UNDYING) && !blocker.canReceiveCounters(CounterType.P1P1) && (blocker
|
|| (blocker.hasKeyword(Keyword.UNDYING) && !blocker.canReceiveCounters(CounterEnumType.P1P1) && (blocker
|
||||||
.getCounters(CounterType.P1P1) == 0))) {
|
.getCounters(CounterEnumType.P1P1) == 0))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2389,7 +2317,7 @@ public class ComputerUtilCombat {
|
|||||||
restDamage = target.staticReplaceDamage(restDamage, source, isCombat);
|
restDamage = target.staticReplaceDamage(restDamage, source, isCombat);
|
||||||
|
|
||||||
// Predict replacement effects
|
// Predict replacement effects
|
||||||
for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
|
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||||
for (final ReplacementEffect re : ca.getReplacementEffects()) {
|
for (final ReplacementEffect re : ca.getReplacementEffects()) {
|
||||||
Map<String, String> params = re.getMapParams();
|
Map<String, String> params = re.getMapParams();
|
||||||
if (!re.getMode().equals(ReplacementType.DamageDone) || !params.containsKey("PreventionEffect")) {
|
if (!re.getMode().equals(ReplacementType.DamageDone) || !params.containsKey("PreventionEffect")) {
|
||||||
@@ -2517,7 +2445,7 @@ public class ComputerUtilCombat {
|
|||||||
final Player controller = combatant.getController();
|
final Player controller = combatant.getController();
|
||||||
for (Card c : controller.getCardsIn(ZoneType.Battlefield)) {
|
for (Card c : controller.getCardsIn(ZoneType.Battlefield)) {
|
||||||
for (SpellAbility ability : c.getAllSpellAbilities()) {
|
for (SpellAbility ability : c.getAllSpellAbilities()) {
|
||||||
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
if (!(ability instanceof AbilityActivated)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ability.getApi() != ApiType.Pump) {
|
if (ability.getApi() != ApiType.Pump) {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public class ComputerUtilCost {
|
|||||||
final CostPutCounter addCounter = (CostPutCounter) part;
|
final CostPutCounter addCounter = (CostPutCounter) part;
|
||||||
final CounterType type = addCounter.getCounter();
|
final CounterType type = addCounter.getCounter();
|
||||||
|
|
||||||
if (type.equals(CounterType.M1M1)) {
|
if (type.equals(CounterEnumType.M1M1)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ public class ComputerUtilCost {
|
|||||||
|
|
||||||
final CounterType type = remCounter.counter;
|
final CounterType type = remCounter.counter;
|
||||||
if (!part.payCostFromSource()) {
|
if (!part.payCostFromSource()) {
|
||||||
if (CounterType.P1P1.equals(type)) {
|
if (CounterEnumType.P1P1.equals(type)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -97,7 +97,7 @@ public class ComputerUtilCost {
|
|||||||
|
|
||||||
// check the sa what the PaymentDecision is.
|
// check the sa what the PaymentDecision is.
|
||||||
// ignore Loyality abilities with Zero as Cost
|
// ignore Loyality abilities with Zero as Cost
|
||||||
if (sa != null && !CounterType.LOYALTY.equals(type)) {
|
if (sa != null && !CounterEnumType.LOYALTY.equals(type)) {
|
||||||
final AiCostDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa);
|
final AiCostDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa);
|
||||||
PaymentDecision pay = decision.visit(remCounter);
|
PaymentDecision pay = decision.visit(remCounter);
|
||||||
if (pay == null || pay.c <= 0) {
|
if (pay == null || pay.c <= 0) {
|
||||||
@@ -106,7 +106,7 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//don't kill the creature
|
//don't kill the creature
|
||||||
if (CounterType.P1P1.equals(type) && source.getLethalDamage() <= 1
|
if (CounterEnumType.P1P1.equals(type) && source.getLethalDamage() <= 1
|
||||||
&& !source.hasKeyword(Keyword.UNDYING)) {
|
&& !source.hasKeyword(Keyword.UNDYING)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -467,9 +467,9 @@ public class ComputerUtilCost {
|
|||||||
if(!meetsRestriction)
|
if(!meetsRestriction)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
try {
|
if (StringUtils.isNumeric(parts[0])) {
|
||||||
extraManaNeeded += Integer.parseInt(parts[0]);
|
extraManaNeeded += Integer.parseInt(parts[0]);
|
||||||
} catch (final NumberFormatException e) {
|
} else {
|
||||||
System.out.println("wrong SpellsNeedExtraMana SVar format on " + c);
|
System.out.println("wrong SpellsNeedExtraMana SVar format on " + c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -480,9 +480,9 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
final String snem = c.getSVar("SpellsNeedExtraManaEffect");
|
final String snem = c.getSVar("SpellsNeedExtraManaEffect");
|
||||||
if (!StringUtils.isBlank(snem)) {
|
if (!StringUtils.isBlank(snem)) {
|
||||||
try {
|
if (StringUtils.isNumeric(snem)) {
|
||||||
extraManaNeeded += Integer.parseInt(snem);
|
extraManaNeeded += Integer.parseInt(snem);
|
||||||
} catch (final NumberFormatException e) {
|
} else {
|
||||||
System.out.println("wrong SpellsNeedExtraManaEffect SVar format on " + c);
|
System.out.println("wrong SpellsNeedExtraManaEffect SVar format on " + c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -529,7 +529,7 @@ public class ComputerUtilCost {
|
|||||||
public boolean apply(Card card) {
|
public boolean apply(Card card) {
|
||||||
boolean hasManaSa = false;
|
boolean hasManaSa = false;
|
||||||
for (final SpellAbility sa : card.getSpellAbilities()) {
|
for (final SpellAbility sa : card.getSpellAbilities()) {
|
||||||
if (sa.isManaAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
if (sa.isManaAbility() && sa.getPayCosts().hasTapCost()) {
|
||||||
hasManaSa = true;
|
hasManaSa = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ public class ComputerUtilMana {
|
|||||||
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
|
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
|
||||||
List<Mana> manaSpentToPay = test ? new ArrayList<>() : sa.getPayingMana();
|
List<Mana> manaSpentToPay = test ? new ArrayList<>() : sa.getPayingMana();
|
||||||
boolean purePhyrexian = cost.containsOnlyPhyrexianMana();
|
boolean purePhyrexian = cost.containsOnlyPhyrexianMana();
|
||||||
int testEnergyPool = ai.getCounters(CounterType.ENERGY);
|
int testEnergyPool = ai.getCounters(CounterEnumType.ENERGY);
|
||||||
|
|
||||||
List<SpellAbility> paymentList = Lists.newArrayList();
|
List<SpellAbility> paymentList = Lists.newArrayList();
|
||||||
|
|
||||||
@@ -507,17 +507,11 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (saPayment.getPayCosts() != null) {
|
|
||||||
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
|
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
|
||||||
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment))) {
|
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment))) {
|
||||||
saList.remove(saPayment);
|
saList.remove(saPayment);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else {
|
|
||||||
System.err.println("Ability " + saPayment + " from " + saPayment.getHostCard() + " had NULL as payCost");
|
|
||||||
saPayment.getHostCard().tap();
|
|
||||||
}
|
|
||||||
|
|
||||||
ai.getGame().getStack().addAndUnfreeze(saPayment);
|
ai.getGame().getStack().addAndUnfreeze(saPayment);
|
||||||
// subtract mana from mana pool
|
// subtract mana from mana pool
|
||||||
@@ -741,7 +735,7 @@ public class ComputerUtilMana {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thisMana.getManaAbility() != null && !thisMana.getManaAbility().meetsManaRestrictions(saBeingPaidFor)) {
|
if (thisMana.getManaAbility() != null && !thisMana.getManaAbility().meetsSpellAndShardRestrictions(saBeingPaidFor, shard, thisMana.getColor())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -837,11 +831,10 @@ public class ComputerUtilMana {
|
|||||||
if (checkCosts) {
|
if (checkCosts) {
|
||||||
// Check if AI can still play this mana ability
|
// Check if AI can still play this mana ability
|
||||||
ma.setActivatingPlayer(ai);
|
ma.setActivatingPlayer(ai);
|
||||||
if (ma.getPayCosts() != null) { // if the AI can't pay the additional costs skip the mana ability
|
// if the AI can't pay the additional costs skip the mana ability
|
||||||
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) {
|
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (sourceCard.isTapped()) {
|
else if (sourceCard.isTapped()) {
|
||||||
return false;
|
return false;
|
||||||
} else if (ma.getRestrictions() != null && ma.getRestrictions().isInstantSpeed()) {
|
} else if (ma.getRestrictions() != null && ma.getRestrictions().isInstantSpeed()) {
|
||||||
@@ -1144,7 +1137,7 @@ public class ComputerUtilMana {
|
|||||||
ManaCostBeingPaid cost = new ManaCostBeingPaid(mana, restriction);
|
ManaCostBeingPaid cost = new ManaCostBeingPaid(mana, restriction);
|
||||||
|
|
||||||
// Tack xMana Payments into mana here if X is a set value
|
// Tack xMana Payments into mana here if X is a set value
|
||||||
if (sa.getPayCosts() != null && (cost.getXcounter() > 0 || extraMana > 0)) {
|
if (cost.getXcounter() > 0 || extraMana > 0) {
|
||||||
int manaToAdd = 0;
|
int manaToAdd = 0;
|
||||||
if (test && extraMana > 0) {
|
if (test && extraMana > 0) {
|
||||||
final int multiplicator = Math.max(cost.getXcounter(), 1);
|
final int multiplicator = Math.max(cost.getXcounter(), 1);
|
||||||
@@ -1169,7 +1162,7 @@ public class ComputerUtilMana {
|
|||||||
cost.increaseShard(shardToGrow, manaToAdd);
|
cost.increaseShard(shardToGrow, manaToAdd);
|
||||||
|
|
||||||
if (!test) {
|
if (!test) {
|
||||||
card.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
sa.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1218,7 +1211,7 @@ public class ComputerUtilMana {
|
|||||||
for (SpellAbility ma : src.getManaAbilities()) {
|
for (SpellAbility ma : src.getManaAbilities()) {
|
||||||
ma.setActivatingPlayer(p);
|
ma.setActivatingPlayer(p);
|
||||||
if (!checkPlayable || ma.canPlay()) {
|
if (!checkPlayable || ma.canPlay()) {
|
||||||
int costsToActivate = ma.getPayCosts() != null && ma.getPayCosts().getCostMana() != null ? ma.getPayCosts().getCostMana().convertAmount() : 0;
|
int costsToActivate = ma.getPayCosts().getCostMana() != null ? ma.getPayCosts().getCostMana().convertAmount() : 0;
|
||||||
int producedMana = ma.getParamOrDefault("Produced", "").split(" ").length;
|
int producedMana = ma.getParamOrDefault("Produced", "").split(" ").length;
|
||||||
int producedAmount = AbilityUtils.calculateAmount(src, ma.getParamOrDefault("Amount", "1"), ma);
|
int producedAmount = AbilityUtils.calculateAmount(src, ma.getParamOrDefault("Amount", "1"), ma);
|
||||||
|
|
||||||
@@ -1594,7 +1587,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int determineMaxAffordableX(Player ai, SpellAbility sa) {
|
public static int determineMaxAffordableX(Player ai, SpellAbility sa) {
|
||||||
if (sa.getPayCosts() == null || sa.getPayCosts().getCostMana() == null) {
|
if (sa.getPayCosts().getCostMana() == null) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import com.google.common.base.Function;
|
|||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.cost.CostPayEnergy;
|
import forge.game.cost.CostPayEnergy;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.keyword.KeywordInterface;
|
import forge.game.keyword.KeywordInterface;
|
||||||
@@ -173,7 +173,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
|||||||
if (c.hasKeyword("CARDNAME can't attack or block.")) {
|
if (c.hasKeyword("CARDNAME can't attack or block.")) {
|
||||||
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
|
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
|
||||||
}
|
}
|
||||||
if (!c.canUntapPhaseController()) {
|
if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
|
||||||
if (c.isTapped()) {
|
if (c.isTapped()) {
|
||||||
value = addValue(50 + (c.getCMC() * 5), "tapped-useless"); // reset everything - useless
|
value = addValue(50 + (c.getCMC() * 5), "tapped-useless"); // reset everything - useless
|
||||||
} else {
|
} else {
|
||||||
@@ -242,11 +242,11 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
|||||||
&& "+X".equals(sa.getParam("NumDef"))
|
&& "+X".equals(sa.getParam("NumDef"))
|
||||||
&& !sa.usesTargeting()
|
&& !sa.usesTargeting()
|
||||||
&& (!sa.hasParam("Defined") || "Self".equals(sa.getParam("Defined")))) {
|
&& (!sa.hasParam("Defined") || "Self".equals(sa.getParam("Defined")))) {
|
||||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
if (sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||||
// Electrostatic Pummeler, can be expanded for similar cards
|
// Electrostatic Pummeler, can be expanded for similar cards
|
||||||
int initPower = getEffectivePower(sa.getHostCard());
|
int initPower = getEffectivePower(sa.getHostCard());
|
||||||
int pumpedPower = initPower;
|
int pumpedPower = initPower;
|
||||||
int energy = sa.getHostCard().getController().getCounters(CounterType.ENERGY);
|
int energy = sa.getHostCard().getController().getCounters(CounterEnumType.ENERGY);
|
||||||
if (energy > 0) {
|
if (energy > 0) {
|
||||||
int numActivations = energy / 3;
|
int numActivations = energy / 3;
|
||||||
for (int i = 0; i < numActivations; i++) {
|
for (int i = 0; i < numActivations; i++) {
|
||||||
|
|||||||
@@ -9,12 +9,10 @@ import forge.card.CardStateName;
|
|||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
|
import forge.game.GameObject;
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
import forge.game.ability.effects.DetachedCardEffect;
|
import forge.game.ability.effects.DetachedCardEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.*;
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardCollectionView;
|
|
||||||
import forge.game.card.CounterType;
|
|
||||||
import forge.game.card.token.TokenInfo;
|
import forge.game.card.token.TokenInfo;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
@@ -24,6 +22,7 @@ import forge.game.mana.ManaPool;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilityManaPart;
|
import forge.game.spellability.AbilityManaPart;
|
||||||
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.ability.AbilityKey;
|
import forge.game.ability.AbilityKey;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
@@ -244,7 +243,7 @@ public abstract class GameState {
|
|||||||
if (card instanceof DetachedCardEffect) {
|
if (card instanceof DetachedCardEffect) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
addCard(zone, card.getOwner() == ai ? aiCardTexts : humanCardTexts, card);
|
addCard(zone, card.getController() == ai ? aiCardTexts : humanCardTexts, card);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,6 +270,10 @@ public abstract class GameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (zoneType == ZoneType.Battlefield) {
|
if (zoneType == ZoneType.Battlefield) {
|
||||||
|
if (c.getOwner() != c.getController()) {
|
||||||
|
// TODO: Handle more than 2-player games.
|
||||||
|
newText.append("|Owner:" + (c.getOwner().isAI() ? "AI" : "Human"));
|
||||||
|
}
|
||||||
if (c.isTapped()) {
|
if (c.isTapped()) {
|
||||||
newText.append("|Tapped");
|
newText.append("|Tapped");
|
||||||
}
|
}
|
||||||
@@ -362,6 +365,12 @@ public abstract class GameState {
|
|||||||
if (c.isFaceDown()) {
|
if (c.isFaceDown()) {
|
||||||
newText.append("|FaceDown"); // Exiled face down
|
newText.append("|FaceDown"); // Exiled face down
|
||||||
}
|
}
|
||||||
|
if (c.isAdventureCard() && c.getZone().is(ZoneType.Exile)) {
|
||||||
|
// TODO: this will basically default all exiled cards with Adventure to being "On Adventure".
|
||||||
|
// Need to figure out a better way to detect if it's actually on adventure.
|
||||||
|
newText.append("|OnAdventure");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zoneType == ZoneType.Battlefield || zoneType == ZoneType.Exile) {
|
if (zoneType == ZoneType.Battlefield || zoneType == ZoneType.Exile) {
|
||||||
@@ -583,6 +592,7 @@ public abstract class GameState {
|
|||||||
cardToEnchantPlayerId.clear();
|
cardToEnchantPlayerId.clear();
|
||||||
cardToRememberedId.clear();
|
cardToRememberedId.clear();
|
||||||
cardToExiledWithId.clear();
|
cardToExiledWithId.clear();
|
||||||
|
cardToImprintedId.clear();
|
||||||
markedDamage.clear();
|
markedDamage.clear();
|
||||||
cardToChosenClrs.clear();
|
cardToChosenClrs.clear();
|
||||||
cardToChosenCards.clear();
|
cardToChosenCards.clear();
|
||||||
@@ -807,6 +817,12 @@ public abstract class GameState {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("RememberTargets")) {
|
||||||
|
for (final GameObject o : sa.getTargets().getTargets()) {
|
||||||
|
sa.getHostCard().addRemembered(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleScriptExecution(final Game game) {
|
private void handleScriptExecution(final Game game) {
|
||||||
@@ -966,6 +982,17 @@ public abstract class GameState {
|
|||||||
spellDef = spellDef.substring(0, spellDef.indexOf("->")).trim();
|
spellDef = spellDef.substring(0, spellDef.indexOf("->")).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Card c = null;
|
||||||
|
|
||||||
|
if (StringUtils.isNumeric(spellDef)) {
|
||||||
|
// Precast from a specific host
|
||||||
|
c = idToCard.get(Integer.parseInt(spellDef));
|
||||||
|
if (c == null) {
|
||||||
|
System.err.println("ERROR: Could not find a card with ID " + spellDef + " to precast!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Precast from a card by name
|
||||||
PaperCard pc = StaticData.instance().getCommonCards().getCard(spellDef);
|
PaperCard pc = StaticData.instance().getCommonCards().getCard(spellDef);
|
||||||
|
|
||||||
if (pc == null) {
|
if (pc == null) {
|
||||||
@@ -973,7 +1000,9 @@ public abstract class GameState {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Card c = Card.fromPaperCard(pc, activator);
|
c = Card.fromPaperCard(pc, activator);
|
||||||
|
}
|
||||||
|
|
||||||
SpellAbility sa = null;
|
SpellAbility sa = null;
|
||||||
|
|
||||||
if (!scriptID.isEmpty()) {
|
if (!scriptID.isEmpty()) {
|
||||||
@@ -1062,11 +1091,11 @@ public abstract class GameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void applyCountersToGameEntity(GameEntity entity, String counterString) {
|
private void applyCountersToGameEntity(GameEntity entity, String counterString) {
|
||||||
entity.setCounters(Maps.newEnumMap(CounterType.class));
|
entity.setCounters(Maps.newHashMap());
|
||||||
String[] allCounterStrings = counterString.split(",");
|
String[] allCounterStrings = counterString.split(",");
|
||||||
for (final String counterPair : allCounterStrings) {
|
for (final String counterPair : allCounterStrings) {
|
||||||
String[] pair = counterPair.split("=", 2);
|
String[] pair = counterPair.split("=", 2);
|
||||||
entity.addCounter(CounterType.valueOf(pair[0]), Integer.parseInt(pair[1]), null, false, false, null);
|
entity.addCounter(CounterType.getType(pair[0]), Integer.parseInt(pair[1]), null, false, false, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1108,7 +1137,7 @@ public abstract class GameState {
|
|||||||
Map<CounterType, Integer> counters = c.getCounters();
|
Map<CounterType, Integer> counters = c.getCounters();
|
||||||
// Note: Not clearCounters() since we want to keep the counters
|
// Note: Not clearCounters() since we want to keep the counters
|
||||||
// var as-is.
|
// var as-is.
|
||||||
c.setCounters(Maps.newEnumMap(CounterType.class));
|
c.setCounters(Maps.newHashMap());
|
||||||
if (c.isAura()) {
|
if (c.isAura()) {
|
||||||
// dummy "enchanting" to indicate that the card will be force-attached elsewhere
|
// dummy "enchanting" to indicate that the card will be force-attached elsewhere
|
||||||
// (will be overridden later, so the actual value shouldn't matter)
|
// (will be overridden later, so the actual value shouldn't matter)
|
||||||
@@ -1202,6 +1231,16 @@ public abstract class GameState {
|
|||||||
c.setState(CardStateName.Flipped, true);
|
c.setState(CardStateName.Flipped, true);
|
||||||
} else if (info.startsWith("Meld")) {
|
} else if (info.startsWith("Meld")) {
|
||||||
c.setState(CardStateName.Meld, true);
|
c.setState(CardStateName.Meld, true);
|
||||||
|
} else if (info.startsWith("OnAdventure")) {
|
||||||
|
String abAdventure = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell";
|
||||||
|
AbilitySub saAdventure = (AbilitySub)AbilityFactory.getAbility(abAdventure, c);
|
||||||
|
StringBuilder sbPlay = new StringBuilder();
|
||||||
|
sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure");
|
||||||
|
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");
|
||||||
|
saAdventure.setSVar("Play", sbPlay.toString());
|
||||||
|
saAdventure.setActivatingPlayer(c.getOwner());
|
||||||
|
saAdventure.resolve();
|
||||||
|
c.setExiledWith(c); // This seems to be the way it's set up internally. Potentially not needed here?
|
||||||
} else if (info.startsWith("IsCommander")) {
|
} else if (info.startsWith("IsCommander")) {
|
||||||
// TODO: This doesn't seem to properly restore the ability to play the commander. Why?
|
// TODO: This doesn't seem to properly restore the ability to play the commander. Why?
|
||||||
c.setCommander(true);
|
c.setCommander(true);
|
||||||
|
|||||||
@@ -145,12 +145,12 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CardCollectionView chooseCardsForEffect(CardCollectionView sourceList, SpellAbility sa, String title, int min, int max, boolean isOptional) {
|
public CardCollectionView chooseCardsForEffect(CardCollectionView sourceList, SpellAbility sa, String title, int min, int max, boolean isOptional, Map<String, Object> params) {
|
||||||
return brains.chooseCardsForEffect(sourceList, sa, min, max, isOptional);
|
return brains.chooseCardsForEffect(sourceList, sa, min, max, isOptional, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T extends GameEntity> T chooseSingleEntityForEffect(FCollectionView<T> optionList, DelayedReveal delayedReveal, SpellAbility sa, String title, boolean isOptional, Player targetedPlayer) {
|
public <T extends GameEntity> T chooseSingleEntityForEffect(FCollectionView<T> optionList, DelayedReveal delayedReveal, SpellAbility sa, String title, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
if (delayedReveal != null) {
|
if (delayedReveal != null) {
|
||||||
reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix());
|
reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix());
|
||||||
}
|
}
|
||||||
@@ -158,13 +158,13 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
if (null == api) {
|
if (null == api) {
|
||||||
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
|
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
|
||||||
}
|
}
|
||||||
return SpellApiToAi.Converter.get(api).chooseSingleEntity(player, sa, (FCollection<T>)optionList, isOptional, targetedPlayer);
|
return SpellApiToAi.Converter.get(api).chooseSingleEntity(player, sa, (FCollection<T>)optionList, isOptional, targetedPlayer, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T extends GameEntity> List<T> chooseEntitiesForEffect(
|
public <T extends GameEntity> List<T> chooseEntitiesForEffect(
|
||||||
FCollectionView<T> optionList, int min, int max, DelayedReveal delayedReveal, SpellAbility sa, String title,
|
FCollectionView<T> optionList, int min, int max, DelayedReveal delayedReveal, SpellAbility sa, String title,
|
||||||
Player targetedPlayer) {
|
Player targetedPlayer, Map<String, Object> params) {
|
||||||
if (delayedReveal != null) {
|
if (delayedReveal != null) {
|
||||||
reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix());
|
reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix());
|
||||||
}
|
}
|
||||||
@@ -172,7 +172,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
List<T> selecteds = new ArrayList<>();
|
List<T> selecteds = new ArrayList<>();
|
||||||
T selected;
|
T selected;
|
||||||
do {
|
do {
|
||||||
selected = chooseSingleEntityForEffect(remaining, null, sa, title, selecteds.size()>=min, targetedPlayer);
|
selected = chooseSingleEntityForEffect(remaining, null, sa, title, selecteds.size()>=min, targetedPlayer, params);
|
||||||
if ( selected != null ) {
|
if ( selected != null ) {
|
||||||
remaining.remove(selected);
|
remaining.remove(selected);
|
||||||
selecteds.add(selected);
|
selecteds.add(selected);
|
||||||
@@ -182,7 +182,23 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpellAbility chooseSingleSpellForEffect(java.util.List<SpellAbility> spells, SpellAbility sa, String title,
|
public List<SpellAbility> chooseSpellAbilitiesForEffect(List<SpellAbility> spells, SpellAbility sa, String title,
|
||||||
|
int num, Map<String, Object> params) {
|
||||||
|
List<SpellAbility> remaining = Lists.newArrayList(spells);
|
||||||
|
List<SpellAbility> selecteds = Lists.newArrayList();
|
||||||
|
SpellAbility selected;
|
||||||
|
do {
|
||||||
|
selected = chooseSingleSpellForEffect(remaining, sa, title, params);
|
||||||
|
if ( selected != null ) {
|
||||||
|
remaining.remove(selected);
|
||||||
|
selecteds.add(selected);
|
||||||
|
}
|
||||||
|
} while ( (selected != null ) && (selecteds.size() < num) );
|
||||||
|
return selecteds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SpellAbility chooseSingleSpellForEffect(List<SpellAbility> spells, SpellAbility sa, String title,
|
||||||
Map<String, Object> params) {
|
Map<String, Object> params) {
|
||||||
ApiType api = sa.getApi();
|
ApiType api = sa.getApi();
|
||||||
if (null == api) {
|
if (null == api) {
|
||||||
@@ -208,15 +224,13 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmTrigger(WrappedAbility wrapper, Map<String, String> triggerParams, boolean isMandatory) {
|
public boolean confirmTrigger(WrappedAbility wrapper) {
|
||||||
final SpellAbility sa = wrapper.getWrappedAbility();
|
final SpellAbility sa = wrapper.getWrappedAbility();
|
||||||
//final Trigger regtrig = wrapper.getTrigger();
|
//final Trigger regtrig = wrapper.getTrigger();
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Deathmist Raptor")) {
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Deathmist Raptor")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (triggerParams.containsKey("DelayedTrigger") || isMandatory) {
|
if (wrapper.isMandatory()) {
|
||||||
//TODO: The only card with an optional delayed trigger is Shirei, Shizo's Caretaker,
|
|
||||||
// needs to be expanded when a more difficult cards comes up
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Store/replace target choices more properly to get this SA cleared.
|
// Store/replace target choices more properly to get this SA cleared.
|
||||||
@@ -502,20 +516,19 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String chooseSomeType(String kindOfType, SpellAbility sa, List<String> validTypes, List<String> invalidTypes, boolean isOptional) {
|
public String chooseSomeType(String kindOfType, SpellAbility sa, Collection<String> validTypes, List<String> invalidTypes, boolean isOptional) {
|
||||||
String chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), invalidTypes);
|
String chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), validTypes, invalidTypes);
|
||||||
if (StringUtils.isBlank(chosen) && !validTypes.isEmpty())
|
if (StringUtils.isBlank(chosen) && !validTypes.isEmpty()) {
|
||||||
{
|
chosen = validTypes.iterator().next();
|
||||||
chosen = validTypes.get(0);
|
System.err.println("AI has no idea how to choose " + kindOfType +", defaulting to arbitrary element: chosen");
|
||||||
System.err.println("AI has no idea how to choose " + kindOfType +", defaulting to 1st element: chosen");
|
|
||||||
}
|
}
|
||||||
game.getAction().nofityOfValue(sa, player, chosen, player);
|
game.getAction().nofityOfValue(sa, player, chosen, player);
|
||||||
return chosen;
|
return chosen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes) {
|
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer) {
|
||||||
return ComputerUtil.vote(player, options, sa, votes);
|
return ComputerUtil.vote(player, options, sa, votes, forPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -604,6 +617,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
if (sa instanceof LandAbility) {
|
if (sa instanceof LandAbility) {
|
||||||
if (sa.canPlay()) {
|
if (sa.canPlay()) {
|
||||||
sa.resolve();
|
sa.resolve();
|
||||||
|
game.updateLastStateForCard(sa.getHostCard());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ComputerUtil.handlePlayingSpellAbility(player, sa, game);
|
ComputerUtil.handlePlayingSpellAbility(player, sa, game);
|
||||||
@@ -755,6 +769,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
return defaultVal != null && defaultVal.booleanValue();
|
return defaultVal != null && defaultVal.booleanValue();
|
||||||
case UntapTimeVault: return false; // TODO Should AI skip his turn for time vault?
|
case UntapTimeVault: return false; // TODO Should AI skip his turn for time vault?
|
||||||
case LeftOrRight: return brains.chooseDirection(sa);
|
case LeftOrRight: return brains.chooseDirection(sa);
|
||||||
|
case OddsOrEvens: return brains.chooseEvenOdd(sa); // false is Odd, true is Even
|
||||||
default:
|
default:
|
||||||
return MyRandom.getRandom().nextBoolean();
|
return MyRandom.getRandom().nextBoolean();
|
||||||
}
|
}
|
||||||
@@ -1123,7 +1138,8 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
CardCollectionView cards = CardLists.getValidCards(aiLibrary, "Creature", player, sa.getHostCard());
|
CardCollectionView cards = CardLists.getValidCards(aiLibrary, "Creature", player, sa.getHostCard());
|
||||||
return ComputerUtilCard.getMostProminentCardName(cards);
|
return ComputerUtilCard.getMostProminentCardName(cards);
|
||||||
} else if (logic.equals("BestCreatureInComputerDeck")) {
|
} else if (logic.equals("BestCreatureInComputerDeck")) {
|
||||||
return ComputerUtilCard.getBestCreatureAI(aiLibrary).getName();
|
Card bestCreature = ComputerUtilCard.getBestCreatureAI(aiLibrary);
|
||||||
|
return bestCreature != null ? bestCreature.getName() : "Plains";
|
||||||
} else if (logic.equals("RandomInComputerDeck")) {
|
} else if (logic.equals("RandomInComputerDeck")) {
|
||||||
return Aggregates.random(aiLibrary).getName();
|
return Aggregates.random(aiLibrary).getName();
|
||||||
} else if (logic.equals("MostProminentSpellInComputerDeck")) {
|
} else if (logic.equals("MostProminentSpellInComputerDeck")) {
|
||||||
@@ -1214,7 +1230,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
public List<OptionalCostValue> chooseOptionalCosts(SpellAbility chosen,
|
public List<OptionalCostValue> chooseOptionalCosts(SpellAbility chosen,
|
||||||
List<OptionalCostValue> optionalCostValues) {
|
List<OptionalCostValue> optionalCostValues) {
|
||||||
List<OptionalCostValue> chosenOptCosts = Lists.newArrayList();
|
List<OptionalCostValue> chosenOptCosts = Lists.newArrayList();
|
||||||
Cost costSoFar = chosen.getPayCosts() != null ? chosen.getPayCosts().copy() : Cost.Zero;
|
Cost costSoFar = chosen.getPayCosts().copy();
|
||||||
|
|
||||||
for (OptionalCostValue opt : optionalCostValues) {
|
for (OptionalCostValue opt : optionalCostValues) {
|
||||||
// Choose the optional cost if it can be paid (to be improved later, check for playability and other conditions perhaps)
|
// Choose the optional cost if it can be paid (to be improved later, check for playability and other conditions perhaps)
|
||||||
@@ -1253,7 +1269,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
// TODO: improve the logic depending on the keyword and the playability of the cost-modified SA (enough targets present etc.)
|
// TODO: improve the logic depending on the keyword and the playability of the cost-modified SA (enough targets present etc.)
|
||||||
int chosenAmount = 0;
|
int chosenAmount = 0;
|
||||||
|
|
||||||
Cost costSoFar = sa.getPayCosts() != null ? sa.getPayCosts().copy() : Cost.Zero;
|
Cost costSoFar = sa.getPayCosts().copy();
|
||||||
|
|
||||||
for (int i = 0; i < max; i++) {
|
for (int i = 0; i < max; i++) {
|
||||||
costSoFar.add(cost);
|
costSoFar.add(cost);
|
||||||
@@ -1269,13 +1285,16 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CardCollection chooseCardsForEffectMultiple(Map<String, CardCollection> validMap, SpellAbility sa, String title) {
|
public CardCollection chooseCardsForEffectMultiple(Map<String, CardCollection> validMap, SpellAbility sa, String title, boolean isOptional) {
|
||||||
CardCollection choices = new CardCollection();
|
CardCollection choices = new CardCollection();
|
||||||
|
|
||||||
for (String mapKey: validMap.keySet()) {
|
for (String mapKey: validMap.keySet()) {
|
||||||
CardCollection cc = validMap.get(mapKey);
|
CardCollection cc = validMap.get(mapKey);
|
||||||
cc.removeAll(choices);
|
cc.removeAll(choices);
|
||||||
choices.add(ComputerUtilCard.getBestAI(cc)); // TODO: should the AI limit itself here with the max number of cards in hand?
|
Card chosen = ComputerUtilCard.getBestAI(cc);
|
||||||
|
if (chosen != null) {
|
||||||
|
choices.add(chosen);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return choices;
|
return choices;
|
||||||
|
|||||||
@@ -327,7 +327,7 @@ public class SpecialCardAi {
|
|||||||
boolean canTrample = source.hasKeyword(Keyword.TRAMPLE);
|
boolean canTrample = source.hasKeyword(Keyword.TRAMPLE);
|
||||||
|
|
||||||
if (!isBlocking && combat.getDefenderByAttacker(source) instanceof Card) {
|
if (!isBlocking && combat.getDefenderByAttacker(source) instanceof Card) {
|
||||||
int loyalty = combat.getDefenderByAttacker(source).getCounters(CounterType.LOYALTY);
|
int loyalty = combat.getDefenderByAttacker(source).getCounters(CounterEnumType.LOYALTY);
|
||||||
int totalDamageToPW = 0;
|
int totalDamageToPW = 0;
|
||||||
for (Card atk : (combat.getAttackersOf(combat.getDefenderByAttacker(source)))) {
|
for (Card atk : (combat.getAttackersOf(combat.getDefenderByAttacker(source)))) {
|
||||||
if (combat.isUnblocked(atk)) {
|
if (combat.isUnblocked(atk)) {
|
||||||
@@ -407,7 +407,7 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Pair<Integer, Integer> getPumpedPT(Player ai, int power, int toughness) {
|
public static Pair<Integer, Integer> getPumpedPT(Player ai, int power, int toughness) {
|
||||||
int energy = ai.getCounters(CounterType.ENERGY);
|
int energy = ai.getCounters(CounterEnumType.ENERGY);
|
||||||
if (energy > 0) {
|
if (energy > 0) {
|
||||||
int numActivations = energy / 3;
|
int numActivations = energy / 3;
|
||||||
for (int i = 0; i < numActivations; i++) {
|
for (int i = 0; i < numActivations; i++) {
|
||||||
@@ -708,7 +708,7 @@ public class SpecialCardAi {
|
|||||||
// if there's another reanimator card currently suspended, don't cast a new one until the previous
|
// if there's another reanimator card currently suspended, don't cast a new one until the previous
|
||||||
// one resolves, otherwise the reanimation attempt will be ruined (e.g. Living End)
|
// one resolves, otherwise the reanimation attempt will be ruined (e.g. Living End)
|
||||||
for (Card ex : ai.getCardsIn(ZoneType.Exile)) {
|
for (Card ex : ai.getCardsIn(ZoneType.Exile)) {
|
||||||
if (ex.hasSVar("IsReanimatorCard") && ex.getCounters(CounterType.TIME) > 0) {
|
if (ex.hasSVar("IsReanimatorCard") && ex.getCounters(CounterEnumType.TIME) > 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -767,7 +767,7 @@ public class SpecialCardAi {
|
|||||||
Player controller = c.getController();
|
Player controller = c.getController();
|
||||||
boolean wasCaged = false;
|
boolean wasCaged = false;
|
||||||
for (Card caged : CardLists.filter(controller.getCardsIn(ZoneType.Exile),
|
for (Card caged : CardLists.filter(controller.getCardsIn(ZoneType.Exile),
|
||||||
CardPredicates.hasCounter(CounterType.CAGE))) {
|
CardPredicates.hasCounter(CounterEnumType.CAGE))) {
|
||||||
if (c.getName().equals(caged.getName())) {
|
if (c.getName().equals(caged.getName())) {
|
||||||
wasCaged = true;
|
wasCaged = true;
|
||||||
break;
|
break;
|
||||||
@@ -1073,7 +1073,7 @@ public class SpecialCardAi {
|
|||||||
// Sarkhan the Mad
|
// Sarkhan the Mad
|
||||||
public static class SarkhanTheMad {
|
public static class SarkhanTheMad {
|
||||||
public static boolean considerDig(final Player ai, final SpellAbility sa) {
|
public static boolean considerDig(final Player ai, final SpellAbility sa) {
|
||||||
return sa.getHostCard().getCounters(CounterType.LOYALTY) == 1;
|
return sa.getHostCard().getCounters(CounterEnumType.LOYALTY) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean considerMakeDragon(final Player ai, final SpellAbility sa) {
|
public static boolean considerMakeDragon(final Player ai, final SpellAbility sa) {
|
||||||
@@ -1109,7 +1109,7 @@ public class SpecialCardAi {
|
|||||||
// Sorin, Vengeful Bloodlord
|
// Sorin, Vengeful Bloodlord
|
||||||
public static class SorinVengefulBloodlord {
|
public static class SorinVengefulBloodlord {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
int loyalty = sa.getHostCard().getCounters(CounterType.LOYALTY);
|
int loyalty = sa.getHostCard().getCounters(CounterEnumType.LOYALTY);
|
||||||
CardCollection creaturesToGet = CardLists.filter(ai.getCardsIn(ZoneType.Graveyard),
|
CardCollection creaturesToGet = CardLists.filter(ai.getCardsIn(ZoneType.Graveyard),
|
||||||
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.lessCMC(loyalty - 1), new Predicate<Card>() {
|
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.lessCMC(loyalty - 1), new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -1295,6 +1295,26 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Timmerian Fiends
|
||||||
|
public static class TimmerianFiends {
|
||||||
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
|
final Card targeted = sa.getParentTargetingCard().getTargetCard();
|
||||||
|
if (targeted == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targeted.isCreature()) {
|
||||||
|
if (ComputerUtil.aiLifeInDanger(ai, true, 0)) {
|
||||||
|
return true; // do it, hoping to save a valuable potential blocker etc.
|
||||||
|
}
|
||||||
|
return ComputerUtilCard.evaluateCreature(targeted) >= 200; // might need tweaking
|
||||||
|
} else {
|
||||||
|
// TODO: this currently compares purely by CMC. To be somehow improved, especially for stuff like the Power Nine etc.
|
||||||
|
return ComputerUtilCard.evaluatePermanentList(new CardCollection(targeted)) >= 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Volrath's Shapeshifter
|
// Volrath's Shapeshifter
|
||||||
public static class VolrathsShapeshifter {
|
public static class VolrathsShapeshifter {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
@@ -1345,7 +1365,7 @@ public class SpecialCardAi {
|
|||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
Game game = source.getGame();
|
Game game = source.getGame();
|
||||||
|
|
||||||
final int loyalty = source.getCounters(CounterType.LOYALTY);
|
final int loyalty = source.getCounters(CounterEnumType.LOYALTY);
|
||||||
int x = -1, best = 0;
|
int x = -1, best = 0;
|
||||||
Card single = null;
|
Card single = null;
|
||||||
for (int i = 0; i < loyalty; i++) {
|
for (int i = 0; i < loyalty; i++) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai;
|
|||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import forge.card.CardStateName;
|
||||||
import forge.card.ICardFace;
|
import forge.card.ICardFace;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
import forge.card.mana.ManaCostParser;
|
import forge.card.mana.ManaCostParser;
|
||||||
@@ -167,7 +168,8 @@ public abstract class SpellAbilityAi {
|
|||||||
|
|
||||||
// a mandatory SpellAbility with targeting but without candidates,
|
// a mandatory SpellAbility with targeting but without candidates,
|
||||||
// does not need to go any deeper
|
// does not need to go any deeper
|
||||||
if (sa.usesTargeting() && mandatory && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
if (sa.usesTargeting() && mandatory && !sa.isTargetNumberValid()
|
||||||
|
&& !sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,6 +249,7 @@ public abstract class SpellAbilityAi {
|
|||||||
protected static boolean isSorcerySpeed(final SpellAbility sa) {
|
protected static boolean isSorcerySpeed(final SpellAbility sa) {
|
||||||
return (sa.getRootAbility().isSpell() && sa.getHostCard().isSorcery())
|
return (sa.getRootAbility().isSpell() && sa.getHostCard().isSorcery())
|
||||||
|| (sa.getRootAbility().isAbility() && sa.getRestrictions().isSorcerySpeed())
|
|| (sa.getRootAbility().isAbility() && sa.getRestrictions().isSorcerySpeed())
|
||||||
|
|| (sa.getRootAbility().isAdventure() && sa.getHostCard().getState(CardStateName.Adventure).getType().isSorcery())
|
||||||
|| (sa.isPwAbility() && !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed."));
|
|| (sa.isPwAbility() && !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed."));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +267,7 @@ public abstract class SpellAbilityAi {
|
|||||||
|
|
||||||
// TODO probably also consider if winter orb or similar are out
|
// TODO probably also consider if winter orb or similar are out
|
||||||
|
|
||||||
if (sa.getPayCosts() == null || sa instanceof AbilitySub) {
|
if (sa instanceof AbilitySub) {
|
||||||
return true; // This is only true for Drawbacks and triggers
|
return true; // This is only true for Drawbacks and triggers
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,7 +307,7 @@ public abstract class SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
|
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
boolean hasPlayer = false;
|
boolean hasPlayer = false;
|
||||||
boolean hasCard = false;
|
boolean hasCard = false;
|
||||||
boolean hasPlaneswalker = false;
|
boolean hasPlaneswalker = false;
|
||||||
@@ -321,11 +324,11 @@ public abstract class SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasPlayer && hasPlaneswalker) {
|
if (hasPlayer && hasPlaneswalker) {
|
||||||
return (T) chooseSinglePlayerOrPlaneswalker(ai, sa, (Collection<GameEntity>) options);
|
return (T) chooseSinglePlayerOrPlaneswalker(ai, sa, (Collection<GameEntity>) options, params);
|
||||||
} else if (hasCard) {
|
} else if (hasCard) {
|
||||||
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer);
|
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer, params);
|
||||||
} else if (hasPlayer) {
|
} else if (hasPlayer) {
|
||||||
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options);
|
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -336,17 +339,17 @@ public abstract class SpellAbilityAi {
|
|||||||
return spells.get(0);
|
return spells.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
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, 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 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 Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||||
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 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options) {
|
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
|
||||||
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 chooseSinglePlayerOrPlaneswalker is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||||
return Iterables.getFirst(options, null);
|
return Iterables.getFirst(options, null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.BidLife, BidLifeAi.class)
|
.put(ApiType.BidLife, BidLifeAi.class)
|
||||||
.put(ApiType.Bond, BondAi.class)
|
.put(ApiType.Bond, BondAi.class)
|
||||||
.put(ApiType.Branch, AlwaysPlayAi.class)
|
.put(ApiType.Branch, AlwaysPlayAi.class)
|
||||||
.put(ApiType.CantUntapTurn, CantUntapTurnAi.class)
|
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
|
||||||
.put(ApiType.ChangeCombatants, CannotPlayAi.class)
|
|
||||||
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
||||||
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
||||||
.put(ApiType.ChangeZone, ChangeZoneAi.class)
|
.put(ApiType.ChangeZone, ChangeZoneAi.class)
|
||||||
@@ -43,6 +42,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.ChooseCard, ChooseCardAi.class)
|
.put(ApiType.ChooseCard, ChooseCardAi.class)
|
||||||
.put(ApiType.ChooseColor, ChooseColorAi.class)
|
.put(ApiType.ChooseColor, ChooseColorAi.class)
|
||||||
.put(ApiType.ChooseDirection, ChooseDirectionAi.class)
|
.put(ApiType.ChooseDirection, ChooseDirectionAi.class)
|
||||||
|
.put(ApiType.ChooseEvenOdd, ChooseEvenOddAi.class)
|
||||||
.put(ApiType.ChooseNumber, ChooseNumberAi.class)
|
.put(ApiType.ChooseNumber, ChooseNumberAi.class)
|
||||||
.put(ApiType.ChoosePlayer, ChoosePlayerAi.class)
|
.put(ApiType.ChoosePlayer, ChoosePlayerAi.class)
|
||||||
.put(ApiType.ChooseSource, ChooseSourceAi.class)
|
.put(ApiType.ChooseSource, ChooseSourceAi.class)
|
||||||
@@ -84,7 +84,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.FlipACoin, FlipACoinAi.class)
|
.put(ApiType.FlipACoin, FlipACoinAi.class)
|
||||||
.put(ApiType.Fog, FogAi.class)
|
.put(ApiType.Fog, FogAi.class)
|
||||||
.put(ApiType.GainControl, ControlGainAi.class)
|
.put(ApiType.GainControl, ControlGainAi.class)
|
||||||
.put(ApiType.GainControlVariant, AlwaysPlayAi.class)
|
.put(ApiType.GainControlVariant, ControlGainVariantAi.class)
|
||||||
.put(ApiType.GainLife, LifeGainAi.class)
|
.put(ApiType.GainLife, LifeGainAi.class)
|
||||||
.put(ApiType.GainOwnership, CannotPlayAi.class)
|
.put(ApiType.GainOwnership, CannotPlayAi.class)
|
||||||
.put(ApiType.GameDrawn, CannotPlayAi.class)
|
.put(ApiType.GameDrawn, CannotPlayAi.class)
|
||||||
@@ -92,6 +92,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.Goad, GoadAi.class)
|
.put(ApiType.Goad, GoadAi.class)
|
||||||
.put(ApiType.Haunt, HauntAi.class)
|
.put(ApiType.Haunt, HauntAi.class)
|
||||||
.put(ApiType.ImmediateTrigger, AlwaysPlayAi.class)
|
.put(ApiType.ImmediateTrigger, AlwaysPlayAi.class)
|
||||||
|
.put(ApiType.Investigate, InvestigateAi.class)
|
||||||
.put(ApiType.LoseLife, LifeLoseAi.class)
|
.put(ApiType.LoseLife, LifeLoseAi.class)
|
||||||
.put(ApiType.LosesGame, GameLossAi.class)
|
.put(ApiType.LosesGame, GameLossAi.class)
|
||||||
.put(ApiType.Mana, ManaEffectAi.class)
|
.put(ApiType.Mana, ManaEffectAi.class)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
@@ -23,12 +25,12 @@ public class AmassAi extends SpellAbilityAi {
|
|||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
if (!aiArmies.isEmpty()) {
|
if (!aiArmies.isEmpty()) {
|
||||||
return CardLists.count(aiArmies, CardPredicates.canReceiveCounters(CounterType.P1P1)) > 0;
|
return CardLists.count(aiArmies, CardPredicates.canReceiveCounters(CounterEnumType.P1P1)) > 0;
|
||||||
} else {
|
} else {
|
||||||
final String tokenScript = "b_0_0_zombie_army";
|
final String tokenScript = "b_0_0_zombie_army";
|
||||||
final int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Num", "1"), sa);
|
final int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Num", "1"), sa);
|
||||||
|
|
||||||
Card token = TokenInfo.getProtoType(tokenScript, sa);
|
Card token = TokenInfo.getProtoType(tokenScript, sa, false);
|
||||||
|
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
return false;
|
return false;
|
||||||
@@ -44,8 +46,8 @@ public class AmassAi extends SpellAbilityAi {
|
|||||||
CardCollection preList = new CardCollection(token);
|
CardCollection preList = new CardCollection(token);
|
||||||
game.getAction().checkStaticAbilities(false, Sets.newHashSet(token), preList);
|
game.getAction().checkStaticAbilities(false, Sets.newHashSet(token), preList);
|
||||||
|
|
||||||
if (token.canReceiveCounters(CounterType.P1P1)) {
|
if (token.canReceiveCounters(CounterEnumType.P1P1)) {
|
||||||
token.setCounters(CounterType.P1P1, amount);
|
token.setCounters(CounterEnumType.P1P1, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token.isCreature() && token.getNetToughness() < 1) {
|
if (token.isCreature() && token.getNetToughness() < 1) {
|
||||||
@@ -86,8 +88,8 @@ public class AmassAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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, Map<String, Object> params) {
|
||||||
Iterable<Card> better = CardLists.filter(options, CardPredicates.canReceiveCounters(CounterType.P1P1));
|
Iterable<Card> better = CardLists.filter(options, CardPredicates.canReceiveCounters(CounterEnumType.P1P1));
|
||||||
if (Iterables.isEmpty(better)) {
|
if (Iterables.isEmpty(better)) {
|
||||||
better = options;
|
better = options;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,7 +247,6 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
final Player ai = sa.getActivatingPlayer();
|
final Player ai = sa.getActivatingPlayer();
|
||||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
final boolean alwaysActivatePWAbility = sa.hasParam("Planeswalker")
|
final boolean alwaysActivatePWAbility = sa.hasParam("Planeswalker")
|
||||||
&& sa.getPayCosts() != null
|
|
||||||
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class)
|
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class)
|
||||||
&& sa.getTargetRestrictions() != null
|
&& sa.getTargetRestrictions() != null
|
||||||
&& sa.getTargetRestrictions().getMinTargets(sa.getHostCard(), sa) == 0;
|
&& sa.getTargetRestrictions().getMinTargets(sa.getHostCard(), sa) == 0;
|
||||||
|
|||||||
@@ -398,7 +398,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
if (!c.isCreature() && !c.getType().hasSubtype("Vehicle") && !c.isTapped()) {
|
if (!c.isCreature() && !c.getType().hasSubtype("Vehicle") && !c.isTapped()) {
|
||||||
// try to identify if this thing can actually tap
|
// try to identify if this thing can actually tap
|
||||||
for (SpellAbility ab : c.getAllSpellAbilities()) {
|
for (SpellAbility ab : c.getAllSpellAbilities()) {
|
||||||
if (ab.getPayCosts() != null && ab.getPayCosts().hasTapCost()) {
|
if (ab.getPayCosts().hasTapCost()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -560,7 +560,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||||
if (sa.isAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1031,7 +1031,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
Card c = null;
|
Card c = null;
|
||||||
List<Card> magnetList = null;
|
List<Card> magnetList = null;
|
||||||
String stCheck = null;
|
String stCheck = null;
|
||||||
if (attachSource.isAura() || sa.hasParam("Bestow")) {
|
if (attachSource.isAura() || sa.isBestow()) {
|
||||||
stCheck = "EnchantedBy";
|
stCheck = "EnchantedBy";
|
||||||
magnetList = CardLists.filter(list, new Predicate<Card>() {
|
magnetList = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -1146,7 +1146,6 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
final List<String> keywords = new ArrayList<>();
|
final List<String> keywords = new ArrayList<>();
|
||||||
boolean grantingAbilities = false;
|
boolean grantingAbilities = false;
|
||||||
boolean grantingExtraBlock = false;
|
boolean grantingExtraBlock = false;
|
||||||
boolean grantingCantUntap = false;
|
|
||||||
|
|
||||||
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
|
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
|
||||||
final Map<String, String> stabMap = stAbility.getMapParams();
|
final Map<String, String> stabMap = stAbility.getMapParams();
|
||||||
@@ -1166,7 +1165,6 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
grantingAbilities |= stabMap.containsKey("AddAbility");
|
grantingAbilities |= stabMap.containsKey("AddAbility");
|
||||||
grantingExtraBlock |= stabMap.containsKey("CanBlockAmount") || stabMap.containsKey("CanBlockAny");
|
grantingExtraBlock |= stabMap.containsKey("CanBlockAmount") || stabMap.containsKey("CanBlockAny");
|
||||||
grantingCantUntap |= stabMap.containsKey("CantUntap");
|
|
||||||
|
|
||||||
String kws = stabMap.get("AddKeyword");
|
String kws = stabMap.get("AddKeyword");
|
||||||
if (kws != null) {
|
if (kws != null) {
|
||||||
@@ -1199,7 +1197,6 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
if (totToughness + totPower < 4 && (!keywords.isEmpty() || grantingExtraBlock)) {
|
if (totToughness + totPower < 4 && (!keywords.isEmpty() || grantingExtraBlock)) {
|
||||||
final int pow = totPower;
|
final int pow = totPower;
|
||||||
final boolean extraBlock = grantingExtraBlock;
|
final boolean extraBlock = grantingExtraBlock;
|
||||||
final boolean cantUntap = grantingCantUntap;
|
|
||||||
prefList = CardLists.filter(prefList, new Predicate<Card>() {
|
prefList = CardLists.filter(prefList, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
@@ -1218,9 +1215,6 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
if (extraBlock && CombatUtil.canBlock(c, true) && !c.canBlockAny()) {
|
if (extraBlock && CombatUtil.canBlock(c, true) && !c.canBlockAny()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (cantUntap && c.isTapped()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1638,6 +1632,8 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
} else if (keyword.endsWith("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|
} else if (keyword.endsWith("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|
||||||
|| keyword.endsWith("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
|
|| keyword.endsWith("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
|
||||||
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 2;
|
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 2;
|
||||||
|
} else if (keyword.endsWith("CARDNAME doesn't untap during your untap step.")) {
|
||||||
|
return !card.isUntapped();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1708,12 +1704,12 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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, Map<String, Object> params) {
|
||||||
return attachToCardAIPreferences(ai, sa, true);
|
return attachToCardAIPreferences(ai, sa, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||||
return attachToPlayerAIPreferences(ai, sa, true);
|
return attachToPlayerAIPreferences(ai, sa, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
*/
|
*/
|
||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
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;
|
||||||
@@ -50,7 +52,7 @@ public final class BondAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
|
|
||||||
@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, Map<String, Object> params) {
|
||||||
return ComputerUtilCard.getBestCreatureAI(options);
|
return ComputerUtilCard.getBestCreatureAI(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
package forge.ai.ability;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
import com.google.common.base.Predicates;
|
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.card.Card;
|
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.card.CardPredicates;
|
|
||||||
import forge.game.cost.CostPutCounter;
|
|
||||||
import forge.game.player.Player;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
|
|
||||||
public class CantUntapTurnAi extends SpellAbilityAi {
|
|
||||||
@Override
|
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
|
||||||
if (sa.usesTargeting()) {
|
|
||||||
CardCollection oppCards = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
|
||||||
|
|
||||||
CardCollection relevantToHold = CardLists.filter(oppCards,
|
|
||||||
Predicates.and(CardPredicates.Presets.TAPPED, new Predicate<Card>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(Card card) {
|
|
||||||
if (card.isCreature()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (final SpellAbility ab : card.getSpellAbilities()) {
|
|
||||||
if (ab.isAbility() && (ab.getPayCosts() != null) && ab.getPayCosts().hasTapCost()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
Card bestToTap = ComputerUtilCard.getBestAI(relevantToHold);
|
|
||||||
Card validTarget = ComputerUtilCard.getBestAI(CardLists.filter(oppCards, CardPredicates.Presets.TAPPED));
|
|
||||||
if (validTarget == null) {
|
|
||||||
validTarget = ComputerUtilCard.getBestAI(oppCards);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bestToTap != null) {
|
|
||||||
sa.getTargets().add(bestToTap);
|
|
||||||
return true;
|
|
||||||
} else if (sa.hasParam("Planeswalker")
|
|
||||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostPutCounter.class)) {
|
|
||||||
sa.getTargets().add(validTarget);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
|
||||||
return mandatory || canPlayAI(aiPlayer, sa);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.GameEntity;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerCollection;
|
||||||
|
import forge.game.player.PlayerPredicates;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ChangeCombatantsAi 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) {
|
||||||
|
// TODO: Extend this if possible for cards that have this as an activated ability
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
return mandatory || canPlayAI(aiPlayer, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (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) {
|
||||||
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
|
if (logic.equals("WeakestOppExceptCtrl")) {
|
||||||
|
PlayerCollection targetableOpps = aiPlayer.getOpponents();
|
||||||
|
targetableOpps.remove(sa.getHostCard().getController());
|
||||||
|
if (targetableOpps.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
|
PlayerCollection targetableOpps = new PlayerCollection();
|
||||||
|
for (GameEntity p : options) {
|
||||||
|
if (p instanceof Player && !p.equals(sa.getHostCard().getController())) {
|
||||||
|
Player pp = (Player)p;
|
||||||
|
if (pp.isOpponentOf(ai)) {
|
||||||
|
targetableOpps.add(pp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Player weakestTargetableOpp = targetableOpps.filter(PlayerPredicates.isTargetableBy(sa))
|
||||||
|
.min(PlayerPredicates.compareByLife());
|
||||||
|
|
||||||
|
return (T)weakestTargetableOpp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1065,8 +1065,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (destination.equals(ZoneType.Exile) || origin.contains(ZoneType.Battlefield)) {
|
if (destination.equals(ZoneType.Exile) || origin.contains(ZoneType.Battlefield)) {
|
||||||
|
|
||||||
// don't rush bouncing stuff when not going to attack
|
// don't rush bouncing stuff when not going to attack
|
||||||
if (!immediately && sa.getPayCosts() != null
|
if (!immediately && game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||||
&& game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
|
||||||
&& game.getPhaseHandler().isPlayerTurn(ai)
|
&& game.getPhaseHandler().isPlayerTurn(ai)
|
||||||
&& ai.getCreaturesInPlay().isEmpty()) {
|
&& ai.getCreaturesInPlay().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1103,8 +1102,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean doWithoutTarget = sa.hasParam("Planeswalker") && sa.getTargetRestrictions() != null
|
boolean doWithoutTarget = sa.hasParam("Planeswalker") && sa.usesTargeting()
|
||||||
&& sa.getTargetRestrictions().getMinTargets(source, sa) == 0 && sa.getPayCosts() != null
|
&& sa.getTargetRestrictions().getMinTargets(source, sa) == 0
|
||||||
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class);
|
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class);
|
||||||
|
|
||||||
if (list.isEmpty() && !doWithoutTarget) {
|
if (list.isEmpty() && !doWithoutTarget) {
|
||||||
@@ -1325,11 +1324,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
Collections.sort(aiPlaneswalkers, new Comparator<Card>() {
|
Collections.sort(aiPlaneswalkers, new Comparator<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(final Card a, final Card b) {
|
public int compare(final Card a, final Card b) {
|
||||||
return a.getCounters(CounterType.LOYALTY) - b.getCounters(CounterType.LOYALTY);
|
return a.getCounters(CounterEnumType.LOYALTY) - b.getCounters(CounterEnumType.LOYALTY);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
for (Card pw : aiPlaneswalkers) {
|
for (Card pw : aiPlaneswalkers) {
|
||||||
int curLoyalty = pw.getCounters(CounterType.LOYALTY);
|
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
|
||||||
int freshLoyalty = Integer.valueOf(pw.getCurrentState().getBaseLoyalty());
|
int freshLoyalty = Integer.valueOf(pw.getCurrentState().getBaseLoyalty());
|
||||||
if (freshLoyalty - curLoyalty >= loyaltyDiff && curLoyalty <= maxLoyaltyToConsider) {
|
if (freshLoyalty - curLoyalty >= loyaltyDiff && curLoyalty <= maxLoyaltyToConsider) {
|
||||||
return pw;
|
return pw;
|
||||||
@@ -1618,7 +1617,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
|
|
||||||
@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, Map<String, Object> params) {
|
||||||
// Called when looking for creature to attach aura or equipment
|
// Called when looking for creature to attach aura or equipment
|
||||||
return ComputerUtilCard.getBestAI(options);
|
return ComputerUtilCard.getBestAI(options);
|
||||||
}
|
}
|
||||||
@@ -1627,7 +1626,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
|
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||||
// Currently only used by Curse of Misfortunes, so this branch should never get hit
|
// Currently only used by Curse of Misfortunes, so this branch should never get hit
|
||||||
// But just in case it does, just select the first option
|
// But just in case it does, just select the first option
|
||||||
return Iterables.getFirst(options, null);
|
return Iterables.getFirst(options, null);
|
||||||
@@ -1790,6 +1789,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean doReturnCommanderLogic(SpellAbility sa, Player aiPlayer) {
|
public boolean doReturnCommanderLogic(SpellAbility sa, Player aiPlayer) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>)sa.getReplacingObject(AbilityKey.OriginalParams);
|
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>)sa.getReplacingObject(AbilityKey.OriginalParams);
|
||||||
SpellAbility causeSa = (SpellAbility)originalParams.get(AbilityKey.Cause);
|
SpellAbility causeSa = (SpellAbility)originalParams.get(AbilityKey.Cause);
|
||||||
SpellAbility causeSub = null;
|
SpellAbility causeSub = null;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import forge.util.MyRandom;
|
|||||||
import forge.util.collect.FCollection;
|
import forge.util.collect.FCollection;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class CharmAi extends SpellAbilityAi {
|
public class CharmAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
@@ -232,7 +233,7 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> opponents) {
|
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> opponents, Map<String, Object> params) {
|
||||||
return Aggregates.random(opponents);
|
return Aggregates.random(opponents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
@@ -20,7 +21,7 @@ import forge.game.card.CardCollectionView;
|
|||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
@@ -99,7 +100,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
});
|
});
|
||||||
return !choices.isEmpty();
|
return !choices.isEmpty();
|
||||||
} else if (aiLogic.equals("Ashiok")) {
|
} else if (aiLogic.equals("Ashiok")) {
|
||||||
final int loyalty = host.getCounters(CounterType.LOYALTY) - 1;
|
final int loyalty = host.getCounters(CounterEnumType.LOYALTY) - 1;
|
||||||
for (int i = loyalty; i >= 0; i--) {
|
for (int i = loyalty; i >= 0; i--) {
|
||||||
host.setSVar("ChosenX", "Number$" + i);
|
host.setSVar("ChosenX", "Number$" + i);
|
||||||
choices = ai.getGame().getCardsIn(choiceZone);
|
choices = ai.getGame().getCardsIn(choiceZone);
|
||||||
@@ -145,7 +146,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final Player ctrl = host.getController();
|
final Player ctrl = host.getController();
|
||||||
final String logic = sa.getParam("AILogic");
|
final String logic = sa.getParam("AILogic");
|
||||||
@@ -233,7 +234,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (SpellAbility sa : c.getAllSpellAbilities()) {
|
for (SpellAbility sa : c.getAllSpellAbilities()) {
|
||||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
if (sa.getPayCosts().hasTapCost()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
@@ -23,7 +24,6 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
Card source = sa.getHostCard();
|
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
// Don't tap creatures that may be able to block
|
// Don't tap creatures that may be able to block
|
||||||
if (ComputerUtil.waitForBlocking(sa)) {
|
if (ComputerUtil.waitForBlocking(sa)) {
|
||||||
@@ -60,7 +60,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||||
*/
|
*/
|
||||||
@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, Map<String, Object> params) {
|
||||||
|
|
||||||
return ComputerUtilCard.getBestAI(options);
|
return ComputerUtilCard.getBestAI(options);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ 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.*;
|
||||||
import forge.game.card.CardCollectionView;
|
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.card.CardPredicates;
|
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -69,9 +66,7 @@ public class ChooseColorAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
} else 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
|
||||||
@@ -80,6 +75,11 @@ public class ChooseColorAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
return permanents.size() > 0 && ph.is(PhaseType.MAIN2, ai);
|
return permanents.size() > 0 && ph.is(PhaseType.MAIN2, ai);
|
||||||
}
|
}
|
||||||
|
} else if (logic.equals("HighestDevotionToColor")) {
|
||||||
|
// currently only works more or less reliably in Main2 to cast own spells
|
||||||
|
if (!ph.is(PhaseType.MAIN2, ai)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ChooseCompanionAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
|
List<Card> cards = Lists.newArrayList(options);
|
||||||
|
if (cards.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.shuffle(cards);
|
||||||
|
return cards.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -46,6 +46,6 @@ public class ChooseDirectionAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@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 mandatory || canPlayAI(ai, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
forge-ai/src/main/java/forge/ai/ability/ChooseEvenOddAi.java
Normal file
36
forge-ai/src/main/java/forge/ai/ability/ChooseEvenOddAi.java
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
public class ChooseEvenOddAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
if (!sa.hasParam("AILogic")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
if (tgt != null) {
|
||||||
|
sa.resetTargets();
|
||||||
|
Player opp = aiPlayer.getWeakestOpponent();
|
||||||
|
if (sa.canTarget(opp)) {
|
||||||
|
sa.getTargets().add(opp);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
|
return chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
return mandatory || canPlayAI(ai, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
SpellAbility counterSA = spells.get(0), tokenSA = spells.get(1);
|
SpellAbility counterSA = spells.get(0), tokenSA = spells.get(1);
|
||||||
|
|
||||||
// check for something which might prevent the counters to be placed on host
|
// check for something which might prevent the counters to be placed on host
|
||||||
if (!host.canReceiveCounters(CounterType.P1P1)) {
|
if (!host.canReceiveCounters(CounterEnumType.P1P1)) {
|
||||||
return tokenSA;
|
return tokenSA;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +256,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
// need a copy for one with extra +1/+1 counter boost,
|
// need a copy for one with extra +1/+1 counter boost,
|
||||||
// without causing triggers to run
|
// without causing triggers to run
|
||||||
final Card copy = CardUtil.getLKICopy(host);
|
final Card copy = CardUtil.getLKICopy(host);
|
||||||
copy.setCounters(CounterType.P1P1, copy.getCounters(CounterType.P1P1) + n);
|
copy.setCounters(CounterEnumType.P1P1, copy.getCounters(CounterEnumType.P1P1) + n);
|
||||||
copy.setZone(host.getZone());
|
copy.setZone(host.getZone());
|
||||||
|
|
||||||
// if host would put into the battlefield attacking
|
// if host would put into the battlefield attacking
|
||||||
@@ -281,10 +281,10 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
// TODO check for trigger to turn token ETB into +1/+1 counter for host
|
// 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
|
// TODO check for trigger to turn token ETB into damage or life loss for opponent
|
||||||
// in this cases Token might be prefered even if they would not survive
|
// in this cases Token might be prefered even if they would not survive
|
||||||
final Card tokenCard = TokenAi.spawnToken(player, tokenSA, true);
|
final Card tokenCard = TokenAi.spawnToken(player, tokenSA);
|
||||||
|
|
||||||
// Token would not survive
|
// Token would not survive
|
||||||
if (tokenCard.getNetToughness() < 1) {
|
if (!tokenCard.isCreature() || tokenCard.getNetToughness() < 1) {
|
||||||
return counterSA;
|
return counterSA;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +362,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
game.getAction().checkStaticAbilities(false);
|
game.getAction().checkStaticAbilities(false);
|
||||||
|
|
||||||
// can't gain counters, use Haste
|
// can't gain counters, use Haste
|
||||||
if (!copy.canReceiveCounters(CounterType.P1P1)) {
|
if (!copy.canReceiveCounters(CounterEnumType.P1P1)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import forge.game.spellability.SpellAbility;
|
|||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class ChoosePlayerAi extends SpellAbilityAi {
|
public class ChoosePlayerAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
@@ -27,7 +28,7 @@ public class ChoosePlayerAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> choices) {
|
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> choices, Map<String, Object> params) {
|
||||||
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) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
@@ -126,7 +127,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
|
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
|
||||||
final Player ai = sa.getActivatingPlayer();
|
final Player ai = sa.getActivatingPlayer();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
|
|
||||||
@@ -56,7 +58,7 @@ public class ClashAi extends SpellAbilityAi {
|
|||||||
* 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, Map<String, Object> params) {
|
||||||
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;
|
||||||
@@ -82,7 +84,7 @@ public class ClashAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
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, null);
|
||||||
if (chosen != null) {
|
if (chosen != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(chosen);
|
sa.getTargets().add(chosen);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import forge.game.spellability.SpellAbility;
|
|||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class CloneAi extends SpellAbilityAi {
|
public class CloneAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -169,7 +170,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||||
Player targetedPlayer) {
|
Player targetedPlayer, Map<String, Object> params) {
|
||||||
|
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final Player ctrl = host.getController();
|
final Player ctrl = host.getController();
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
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.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
@@ -34,12 +35,12 @@ 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.PlayerCollection;
|
||||||
import forge.game.player.PlayerPredicates;
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
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
|
||||||
@@ -54,8 +55,6 @@ import forge.util.collect.FCollectionView;
|
|||||||
// (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
|
|
||||||
// NoRegen - set if destroyed creature can't be regenerated. used only with DestroyTgt
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -77,7 +76,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
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 PlayerCollection 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) {
|
||||||
@@ -94,20 +93,21 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
sa.setTargetingPlayer(targetingPlayer);
|
sa.setTargetingPlayer(targetingPlayer);
|
||||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||||
}
|
}
|
||||||
if (tgt.isRandomTarget()) {
|
|
||||||
sa.getTargets().add(Aggregates.random(tgt.getAllCandidates(sa, false)));
|
|
||||||
}
|
|
||||||
if (tgt.canOnlyTgtOpponent()) {
|
if (tgt.canOnlyTgtOpponent()) {
|
||||||
List<Player> oppList = Lists
|
List<Player> oppList = opponents.filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
.newArrayList(Iterables.filter(opponents, PlayerPredicates.isTargetableBy(sa)));
|
|
||||||
|
|
||||||
if (oppList.isEmpty()) {
|
if (oppList.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tgt.isRandomTarget()) {
|
||||||
|
sa.getTargets().add(Aggregates.random(oppList));
|
||||||
|
} else {
|
||||||
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
|
||||||
@@ -303,7 +303,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
} // pumpDrawbackAI()
|
} // pumpDrawbackAI()
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||||
final List<Card> cards = Lists.newArrayList();
|
final List<Card> cards = Lists.newArrayList();
|
||||||
for (Player p : options) {
|
for (Player p : options) {
|
||||||
cards.addAll(p.getCreaturesInPlay());
|
cards.addAll(p.getCreaturesInPlay());
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Forge: Play Magic: the Gathering.
|
||||||
|
* Copyright (C) 2011 Forge Team
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.base.Predicates;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* AbilityFactory_GainControlVariant class.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Forge
|
||||||
|
* @version $Id: AbilityFactoryGainControl.java 17764 2012-10-29 11:04:18Z Sloth $
|
||||||
|
*/
|
||||||
|
public class ControlGainVariantAi extends SpellAbilityAi {
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||||
|
|
||||||
|
String logic = sa.getParam("AILogic");
|
||||||
|
|
||||||
|
if ("GainControlOwns".equals(logic)) {
|
||||||
|
List<Card> list = CardLists.filter(ai.getGame().getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final Card crd) {
|
||||||
|
return crd.isCreature() && !crd.getController().equals(crd.getOwner());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (final Card c : list) {
|
||||||
|
if (ai.equals(c.getController())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
|
Iterable<Card> otherCtrl = CardLists.filter(options, Predicates.not(CardPredicates.isController(ai)));
|
||||||
|
if (Iterables.isEmpty(otherCtrl)) {
|
||||||
|
return ComputerUtilCard.getWorstAI(options);
|
||||||
|
} else {
|
||||||
|
return ComputerUtilCard.getBestAI(otherCtrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import com.google.common.base.Predicates;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
|
import forge.game.ability.AbilityKey;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
@@ -18,6 +19,7 @@ import forge.game.zone.ZoneType;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class CopyPermanentAi extends SpellAbilityAi {
|
public class CopyPermanentAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
@@ -180,6 +182,13 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
// if no targeting, it should always be ok
|
// if no targeting, it should always be ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ("TriggeredCardController".equals(sa.getParam("Controller"))) {
|
||||||
|
Card trigCard = (Card)sa.getTriggeringObject(AbilityKey.Card);
|
||||||
|
if (!mandatory && trigCard != null && trigCard.getController().isOpponentOf(aiPlayer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +205,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Card chooseSingleCard(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, Map<String, Object> params) {
|
||||||
// Select a card to attach to
|
// Select a card to attach to
|
||||||
CardCollection betterOptions = getBetterOptions(ai, sa, options, isOptional);
|
CardCollection betterOptions = getBetterOptions(ai, sa, options, isOptional);
|
||||||
if (!betterOptions.isEmpty()) {
|
if (!betterOptions.isEmpty()) {
|
||||||
@@ -215,7 +224,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||||
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay();
|
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay();
|
||||||
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
|
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
|
||||||
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
|
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
final SpellAbility top = game.getStack().peekAbility();
|
final SpellAbility top = game.getStack().peekAbility();
|
||||||
if (top != null
|
if (top != null
|
||||||
&& top.getPayCosts() != null && top.getPayCosts().getCostMana() != null
|
&& top.getPayCosts().getCostMana() != null
|
||||||
&& sa.getPayCosts() != null && sa.getPayCosts().getCostMana() != null
|
&& sa.getPayCosts().getCostMana() != null
|
||||||
&& top.getPayCosts().getCostMana().getMana().getCMC() >= sa.getPayCosts().getCostMana().getMana().getCMC() + diff) {
|
&& top.getPayCosts().getCostMana().getMana().getCMC() >= sa.getPayCosts().getCostMana().getMana().getCMC() + diff) {
|
||||||
// The copied spell has a significantly higher CMC than the copy spell, consider copying
|
// The copied spell has a significantly higher CMC than the copy spell, consider copying
|
||||||
chance = 100;
|
chance = 100;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ 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.CounterType;
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ public abstract class CountersAi {
|
|||||||
final CardCollection boon = CardLists.filter(list, new Predicate<Card>() {
|
final CardCollection boon = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return c.getCounters(CounterType.DIVINITY) == 0;
|
return c.getCounters(CounterEnumType.DIVINITY) == 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(boon, null, false);
|
choice = ComputerUtilCard.getMostExpensivePermanentAI(boon, null, false);
|
||||||
|
|||||||
@@ -42,14 +42,14 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final String type = sa.getParam("CounterType");
|
final String type = sa.getParam("CounterType");
|
||||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
final CounterType cType = "Any".equals(type) ? null : CounterType.getType(type);
|
||||||
|
|
||||||
// Don't tap creatures that may be able to block
|
// Don't tap creatures that may be able to block
|
||||||
if (ComputerUtil.waitForBlocking(sa)) {
|
if (ComputerUtil.waitForBlocking(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CounterType.P1P1.equals(cType) && sa.hasParam("Source")) {
|
if (CounterEnumType.P1P1.equals(cType) && sa.hasParam("Source")) {
|
||||||
int amount = calcAmount(sa, cType);
|
int amount = calcAmount(sa, cType);
|
||||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||||
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||||
@@ -92,7 +92,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
// for Simic Fluxmage and other
|
// for Simic Fluxmage and other
|
||||||
return ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN);
|
return ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN);
|
||||||
|
|
||||||
} else if (CounterType.P1P1.equals(cType) && sa.hasParam("Defined")) {
|
} else if (CounterEnumType.P1P1.equals(cType) && sa.hasParam("Defined")) {
|
||||||
// something like Cyptoplast Root-kin
|
// something like Cyptoplast Root-kin
|
||||||
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||||
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
@@ -115,6 +115,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
|
sa.resetTargets();
|
||||||
|
|
||||||
if (!moveTgtAI(ai, sa) && !mandatory) {
|
if (!moveTgtAI(ai, sa) && !mandatory) {
|
||||||
return false;
|
return false;
|
||||||
@@ -142,7 +143,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
|
|
||||||
final String type = sa.getParam("CounterType");
|
final String type = sa.getParam("CounterType");
|
||||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
final CounterType cType = "Any".equals(type) ? null : CounterType.getType(type);
|
||||||
|
|
||||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||||
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
|
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
|
||||||
@@ -189,7 +190,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// check for some specific AI preferences
|
// check for some specific AI preferences
|
||||||
if ("DontMoveCounterIfLethal".equals(sa.getParam("AILogic"))) {
|
if ("DontMoveCounterIfLethal".equals(sa.getParam("AILogic"))) {
|
||||||
return cType != CounterType.P1P1 || src.getNetToughness() - src.getTempToughnessBoost() - 1 > 0;
|
return !cType.is(CounterEnumType.P1P1) || src.getNetToughness() - src.getTempToughnessBoost() - 1 > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// no target
|
// no target
|
||||||
@@ -234,7 +235,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final String type = sa.getParam("CounterType");
|
final String type = sa.getParam("CounterType");
|
||||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
final CounterType cType = "Any".equals(type) || "All".equals(type) ? null : CounterType.getType(type);
|
||||||
|
|
||||||
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
|
||||||
@@ -278,7 +279,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// do not steal a P1P1 from Undying if it would die
|
// do not steal a P1P1 from Undying if it would die
|
||||||
// this way
|
// this way
|
||||||
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
if (CounterEnumType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
||||||
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken();
|
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -321,13 +322,13 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// try to remove P1P1 from undying or evolve
|
// try to remove P1P1 from undying or evolve
|
||||||
if (CounterType.P1P1.equals(cType)) {
|
if (CounterEnumType.P1P1.equals(cType)) {
|
||||||
if (card.hasKeyword(Keyword.UNDYING) || card.hasKeyword(Keyword.EVOLVE)
|
if (card.hasKeyword(Keyword.UNDYING) || card.hasKeyword(Keyword.EVOLVE)
|
||||||
|| card.hasKeyword(Keyword.ADAPT)) {
|
|| card.hasKeyword(Keyword.ADAPT)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (CounterType.M1M1.equals(cType) && card.hasKeyword(Keyword.PERSIST)) {
|
if (CounterEnumType.M1M1.equals(cType) && card.hasKeyword(Keyword.PERSIST)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,10 +383,10 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cType != null) {
|
if (cType != null) {
|
||||||
if (CounterType.P1P1.equals(cType) && card.hasKeyword(Keyword.UNDYING)) {
|
if (CounterEnumType.P1P1.equals(cType) && card.hasKeyword(Keyword.UNDYING)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (CounterType.M1M1.equals(cType) && card.hasKeyword(Keyword.PERSIST)) {
|
if (CounterEnumType.M1M1.equals(cType) && card.hasKeyword(Keyword.PERSIST)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,7 +394,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -452,7 +453,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
// or for source -> multiple defined
|
// or for source -> multiple defined
|
||||||
@Override
|
@Override
|
||||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||||
Player targetedPlayer) {
|
Player targetedPlayer, Map<String, Object> params) {
|
||||||
if (sa.hasParam("AiLogic")) {
|
if (sa.hasParam("AiLogic")) {
|
||||||
String logic = sa.getParam("AiLogic");
|
String logic = sa.getParam("AiLogic");
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ 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.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
@@ -77,7 +78,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||||
final CounterType counterType = getCounterType(sa);
|
final CounterType counterType = getCounterType(sa);
|
||||||
|
|
||||||
if (!CounterType.P1P1.equals(counterType) && counterType != null) {
|
if (!CounterEnumType.P1P1.equals(counterType) && counterType != null) {
|
||||||
if (!sa.hasParam("ActivationPhases")) {
|
if (!sa.hasParam("ActivationPhases")) {
|
||||||
// Don't use non P1P1/M1M1 counters before main 2 if possible
|
// Don't use non P1P1/M1M1 counters before main 2 if possible
|
||||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||||
@@ -147,15 +148,15 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
if (!aiList.isEmpty()) {
|
if (!aiList.isEmpty()) {
|
||||||
// counter type list to check
|
// counter type list to check
|
||||||
// first loyalty, then P1P!, then Charge Counter
|
// first loyalty, then P1P!, then Charge Counter
|
||||||
List<CounterType> typeList = Lists.newArrayList(CounterType.LOYALTY, CounterType.P1P1, CounterType.CHARGE);
|
List<CounterEnumType> typeList = Lists.newArrayList(CounterEnumType.LOYALTY, CounterEnumType.P1P1, CounterEnumType.CHARGE);
|
||||||
for (CounterType type : typeList) {
|
for (CounterEnumType type : typeList) {
|
||||||
// enough targets
|
// enough targets
|
||||||
if (!sa.canAddMoreTarget()) {
|
if (!sa.canAddMoreTarget()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (counterType == null || counterType == type) {
|
if (counterType == null || counterType.is(type)) {
|
||||||
addTargetsByCounterType(ai, sa, aiList, type);
|
addTargetsByCounterType(ai, sa, aiList, CounterType.get(type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,7 +165,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
if (!oppList.isEmpty()) {
|
if (!oppList.isEmpty()) {
|
||||||
// not enough targets
|
// not enough targets
|
||||||
if (sa.canAddMoreTarget()) {
|
if (sa.canAddMoreTarget()) {
|
||||||
final CounterType type = CounterType.M1M1;
|
final CounterType type = CounterType.get(CounterEnumType.M1M1);
|
||||||
if (counterType == null || counterType == type) {
|
if (counterType == null || counterType == type) {
|
||||||
addTargetsByCounterType(ai, sa, oppList, type);
|
addTargetsByCounterType(ai, sa, oppList, type);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import forge.game.GameEntity;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
|
import forge.game.card.CounterEnumType;
|
||||||
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;
|
||||||
@@ -32,7 +33,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
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(CounterEnumType.EXPERIENCE) + p.getCounters(CounterEnumType.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>() {
|
||||||
@@ -115,17 +116,19 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
|
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
// Proliferate is always optional for all, no need to select best
|
// Proliferate is always optional for all, no need to select best
|
||||||
|
|
||||||
|
final CounterType poison = CounterType.get(CounterEnumType.POISON);
|
||||||
|
|
||||||
// because countertype can't be chosen anymore, only look for posion counters
|
// because countertype can't be chosen anymore, only look for posion counters
|
||||||
for (final Player p : Iterables.filter(options, Player.class)) {
|
for (final Player p : Iterables.filter(options, Player.class)) {
|
||||||
if (p.isOpponentOf(ai)) {
|
if (p.isOpponentOf(ai)) {
|
||||||
if (p.getCounters(CounterType.POISON) > 0 && p.canReceiveCounters(CounterType.POISON)) {
|
if (p.getCounters(poison) > 0 && p.canReceiveCounters(poison)) {
|
||||||
return (T)p;
|
return (T)p;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (p.getCounters(CounterType.POISON) <= 5 || p.canReceiveCounters(CounterType.POISON)) {
|
if (p.getCounters(poison) <= 5 || p.canReceiveCounters(poison)) {
|
||||||
return (T)p;
|
return (T)p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,17 +56,17 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
if (part instanceof CostRemoveCounter) {
|
if (part instanceof CostRemoveCounter) {
|
||||||
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
|
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
|
||||||
final CounterType counterType = remCounter.counter;
|
final CounterType counterType = remCounter.counter;
|
||||||
if (counterType.name().equals(type) && !aiLogic.startsWith("MoveCounter")) {
|
if (counterType.getName().equals(type) && !aiLogic.startsWith("MoveCounter")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!part.payCostFromSource()) {
|
if (!part.payCostFromSource()) {
|
||||||
if (counterType.equals(CounterType.P1P1)) {
|
if (counterType.is(CounterEnumType.P1P1)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// don't kill the creature
|
// don't kill the creature
|
||||||
if (counterType.equals(CounterType.P1P1) && source.getLethalDamage() <= 1) {
|
if (counterType.is(CounterEnumType.P1P1) && source.getLethalDamage() <= 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
int maxLevel = Integer.parseInt(sa.getParam("MaxLevel"));
|
int maxLevel = Integer.parseInt(sa.getParam("MaxLevel"));
|
||||||
return source.getCounters(CounterType.LEVEL) < maxLevel;
|
return source.getCounters(CounterEnumType.LEVEL) < maxLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.checkPhaseRestrictions(ai, sa, ph);
|
return super.checkPhaseRestrictions(ai, sa, ph);
|
||||||
@@ -146,7 +146,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
if (abTgt.canTgtPlayer()) {
|
if (abTgt.canTgtPlayer()) {
|
||||||
// try to kill opponent with Poison
|
// try to kill opponent with Poison
|
||||||
PlayerCollection oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
PlayerCollection oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
PlayerCollection poisonList = oppList.filter(PlayerPredicates.hasCounter(CounterType.POISON, 9));
|
PlayerCollection poisonList = oppList.filter(PlayerPredicates.hasCounter(CounterEnumType.POISON, 9));
|
||||||
if (!poisonList.isEmpty()) {
|
if (!poisonList.isEmpty()) {
|
||||||
sa.getTargets().add(poisonList.max(PlayerPredicates.compareByLife()));
|
sa.getTargets().add(poisonList.max(PlayerPredicates.compareByLife()));
|
||||||
return true;
|
return true;
|
||||||
@@ -157,13 +157,13 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
// try to kill creature with -1/-1 counters if it can
|
// try to kill creature with -1/-1 counters if it can
|
||||||
// receive counters, execpt it has undying
|
// receive counters, execpt it has undying
|
||||||
CardCollection oppCreat = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
|
CardCollection oppCreat = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
|
||||||
CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterType.M1M1));
|
CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterEnumType.M1M1));
|
||||||
oppCreatM1 = CardLists.getNotKeyword(oppCreatM1, Keyword.UNDYING);
|
oppCreatM1 = CardLists.getNotKeyword(oppCreatM1, Keyword.UNDYING);
|
||||||
|
|
||||||
oppCreatM1 = CardLists.filter(oppCreatM1, new Predicate<Card>() {
|
oppCreatM1 = CardLists.filter(oppCreatM1, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Card input) {
|
public boolean apply(Card input) {
|
||||||
return input.getNetToughness() <= 1 && input.canReceiveCounters(CounterType.M1M1);
|
return input.getNetToughness() <= 1 && input.canReceiveCounters(CounterType.get(CounterEnumType.M1M1));
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -220,6 +220,8 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if ("Never".equals(logic)) {
|
if ("Never".equals(logic)) {
|
||||||
return false;
|
return false;
|
||||||
|
} else if ("AlwaysWithNoTgt".equals(logic)) {
|
||||||
|
return true;
|
||||||
} else if ("AristocratCounters".equals(logic)) {
|
} else if ("AristocratCounters".equals(logic)) {
|
||||||
return PumpAi.doAristocratWithCountersLogic(sa, ai);
|
return PumpAi.doAristocratWithCountersLogic(sa, ai);
|
||||||
} else if ("PayEnergy".equals(logic)) {
|
} else if ("PayEnergy".equals(logic)) {
|
||||||
@@ -242,7 +244,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
int totBlkPower = Aggregates.sum(blocked, CardPredicates.Accessors.fnGetNetPower);
|
int totBlkPower = Aggregates.sum(blocked, CardPredicates.Accessors.fnGetNetPower);
|
||||||
int totBlkToughness = Aggregates.min(blocked, CardPredicates.Accessors.fnGetNetToughness);
|
int totBlkToughness = Aggregates.min(blocked, CardPredicates.Accessors.fnGetNetToughness);
|
||||||
|
|
||||||
int numActivations = ai.getCounters(CounterType.ENERGY) / sa.getPayCosts().getCostEnergy().convertAmount();
|
int numActivations = ai.getCounters(CounterEnumType.ENERGY) / sa.getPayCosts().getCostEnergy().convertAmount();
|
||||||
if (sa.getHostCard().getNetToughness() + numActivations > totBlkPower
|
if (sa.getHostCard().getNetToughness() + numActivations > totBlkPower
|
||||||
|| sa.getHostCard().getNetPower() + numActivations >= totBlkToughness) {
|
|| sa.getHostCard().getNetPower() + numActivations >= totBlkToughness) {
|
||||||
return true;
|
return true;
|
||||||
@@ -257,7 +259,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN);
|
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (ai.getCounters(CounterType.ENERGY) > ComputerUtilCard.getMaxSAEnergyCostOnBattlefield(ai) + sa.getPayCosts().getCostEnergy().convertAmount()) {
|
} else if (ai.getCounters(CounterEnumType.ENERGY) > ComputerUtilCard.getMaxSAEnergyCostOnBattlefield(ai) + sa.getPayCosts().getCostEnergy().convertAmount()) {
|
||||||
// outside of combat, this logic only works if the relevant AI profile option is enabled
|
// outside of combat, this logic only works if the relevant AI profile option is enabled
|
||||||
// and if there is enough energy saved
|
// and if there is enough energy saved
|
||||||
if (!onlyInCombat) {
|
if (!onlyInCombat) {
|
||||||
@@ -320,7 +322,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
Game game = ai.getGame();
|
Game game = ai.getGame();
|
||||||
Combat combat = game.getCombat();
|
Combat combat = game.getCombat();
|
||||||
|
|
||||||
if (!source.canReceiveCounters(CounterType.P1P1) || source.getCounters(CounterType.P1P1) > 0) {
|
if (!source.canReceiveCounters(CounterType.get(CounterEnumType.P1P1)) || source.getCounters(CounterEnumType.P1P1) > 0) {
|
||||||
return false;
|
return false;
|
||||||
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
return doCombatAdaptLogic(source, amount, combat);
|
return doCombatAdaptLogic(source, amount, combat);
|
||||||
@@ -343,7 +345,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
if (isClockwork) {
|
if (isClockwork) {
|
||||||
// Clockwork Avian and other similar cards: do not tap all mana for X,
|
// Clockwork Avian and other similar cards: do not tap all mana for X,
|
||||||
// instead only rewind to max allowed value when the power gets low enough.
|
// instead only rewind to max allowed value when the power gets low enough.
|
||||||
int curCtrs = source.getCounters(CounterType.P1P0);
|
int curCtrs = source.getCounters(CounterEnumType.P1P0);
|
||||||
int maxCtrs = Integer.parseInt(sa.getParam("MaxFromEffect"));
|
int maxCtrs = Integer.parseInt(sa.getParam("MaxFromEffect"));
|
||||||
|
|
||||||
// This will "rewind" clockwork cards when they fall to 50% power or below, consider improving
|
// This will "rewind" clockwork cards when they fall to 50% power or below, consider improving
|
||||||
@@ -433,7 +435,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
if (sacSelf && c.equals(source)) {
|
if (sacSelf && c.equals(source)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return sa.canTarget(c) && c.canReceiveCounters(CounterType.valueOf(type));
|
return sa.canTarget(c) && c.canReceiveCounters(CounterType.getType(type));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -452,7 +454,6 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
// but try to do it in Main 2 then so that the AI has a chance to play creatures first.
|
// but try to do it in Main 2 then so that the AI has a chance to play creatures first.
|
||||||
if (list.isEmpty()
|
if (list.isEmpty()
|
||||||
&& sa.hasParam("Planeswalker")
|
&& sa.hasParam("Planeswalker")
|
||||||
&& sa.getPayCosts() != null
|
|
||||||
&& sa.getPayCosts().hasOnlySpecificCostType(CostPutCounter.class)
|
&& sa.getPayCosts().hasOnlySpecificCostType(CostPutCounter.class)
|
||||||
&& sa.isTargetNumberValid()
|
&& sa.isTargetNumberValid()
|
||||||
&& sa.getTargets().getNumTargeted() == 0
|
&& sa.getTargets().getNumTargeted() == 0
|
||||||
@@ -557,7 +558,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int currCounters = cards.get(0).getCounters(CounterType.valueOf(type));
|
final int currCounters = cards.get(0).getCounters(CounterType.get(type));
|
||||||
// each non +1/+1 counter on the card is a 10% chance of not
|
// each non +1/+1 counter on the card is a 10% chance of not
|
||||||
// activating this ability.
|
// activating this ability.
|
||||||
|
|
||||||
@@ -704,7 +705,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
SpellAbility testSa = sa;
|
SpellAbility testSa = sa;
|
||||||
int countX = 0;
|
int countX = 0;
|
||||||
int nonXGlyphs = 0;
|
int nonXGlyphs = 0;
|
||||||
while (testSa != null && testSa.getPayCosts() != null && countX == 0) {
|
while (testSa != null && countX == 0) {
|
||||||
countX = testSa.getPayCosts().getTotalMana().countX();
|
countX = testSa.getPayCosts().getTotalMana().countX();
|
||||||
nonXGlyphs = testSa.getPayCosts().getTotalMana().getGlyphCount() - countX;
|
nonXGlyphs = testSa.getPayCosts().getTotalMana().getGlyphCount() - countX;
|
||||||
testSa = testSa.getSubAbility();
|
testSa = testSa.getSubAbility();
|
||||||
@@ -732,6 +733,21 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
// put a counter?
|
// put a counter?
|
||||||
// things like Powder Keg, which are way too complex for the AI
|
// things like Powder Keg, which are way too complex for the AI
|
||||||
}
|
}
|
||||||
|
} else if (sa.getTargetRestrictions().canOnlyTgtOpponent() && !sa.getTargetRestrictions().canTgtCreature()) {
|
||||||
|
// can only target opponent
|
||||||
|
List<Player> playerList = Lists.newArrayList(Iterables.filter(
|
||||||
|
sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class));
|
||||||
|
|
||||||
|
if (playerList.isEmpty() && mandatory) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to choose player with less creatures
|
||||||
|
Player choice = Collections.min(playerList, PlayerPredicates.compareByZoneSize(ZoneType.Battlefield, CardPredicates.Presets.CREATURES));
|
||||||
|
|
||||||
|
if (choice != null) {
|
||||||
|
sa.getTargets().add(choice);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||||
@@ -873,7 +889,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||||
// used by Tribute, select player with lowest Life
|
// used by Tribute, select player with lowest Life
|
||||||
// TODO add more logic using TributeAILogic
|
// TODO add more logic using TributeAILogic
|
||||||
List<Player> list = Lists.newArrayList(options);
|
List<Player> list = Lists.newArrayList(options);
|
||||||
@@ -881,16 +897,20 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
protected Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
// Bolster does use this
|
// Bolster does use this
|
||||||
// TODO need more or less logic there?
|
// TODO need more or less logic there?
|
||||||
|
final CounterType m1m1 = CounterType.get(CounterEnumType.M1M1);
|
||||||
|
final CounterType p1p1 = CounterType.get(CounterEnumType.P1P1);
|
||||||
|
|
||||||
// no logic if there is no options or no to choice
|
// no logic if there is no options or no to choice
|
||||||
if (!isOptional && Iterables.size(options) <= 1) {
|
if (!isOptional && Iterables.size(options) <= 1) {
|
||||||
return Iterables.getFirst(options, null);
|
return Iterables.getFirst(options, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
|
final CounterType type = params.containsKey("CounterType") ? (CounterType)params.get("CounterType")
|
||||||
|
: CounterType.getType(sa.getParam("CounterType"));
|
||||||
|
|
||||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
|
|
||||||
@@ -907,7 +927,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
if (ComputerUtilCard.isUselessCreature(ai, input))
|
if (ComputerUtilCard.isUselessCreature(ai, input))
|
||||||
return false;
|
return false;
|
||||||
if (CounterType.M1M1.equals(type) && amount >= input.getNetToughness())
|
if (type.is(CounterEnumType.M1M1) && amount >= input.getNetToughness())
|
||||||
return true;
|
return true;
|
||||||
return ComputerUtil.isNegativeCounter(type, input);
|
return ComputerUtil.isNegativeCounter(type, input);
|
||||||
}
|
}
|
||||||
@@ -931,6 +951,20 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
CardCollection filtered = mine;
|
CardCollection filtered = mine;
|
||||||
|
|
||||||
|
// Try to filter out keywords that we already have on cards
|
||||||
|
if (type.isKeywordCounter()) {
|
||||||
|
Keyword kw = Keyword.smartValueOf(type.getName());
|
||||||
|
final CardCollection doNotHaveKeyword = CardLists.filter(filtered, new Predicate<Card>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(Card card) {
|
||||||
|
return !card.hasKeyword(kw) && card.canBeTargetedBy(sa) && sa.canTarget(card);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (doNotHaveKeyword.size() > 0)
|
||||||
|
filtered = doNotHaveKeyword;
|
||||||
|
}
|
||||||
|
|
||||||
final CardCollection notUseless = CardLists.filter(filtered, new Predicate<Card>() {
|
final CardCollection notUseless = CardLists.filter(filtered, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Card input) {
|
public boolean apply(Card input) {
|
||||||
@@ -945,26 +979,26 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// some special logic to reload Persist/Undying
|
// some special logic to reload Persist/Undying
|
||||||
if (CounterType.P1P1.equals(type)) {
|
if (p1p1.equals(type)) {
|
||||||
final CardCollection persist = CardLists.filter(filtered, new Predicate<Card>() {
|
final CardCollection persist = CardLists.filter(filtered, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Card input) {
|
public boolean apply(Card input) {
|
||||||
if (!input.hasKeyword(Keyword.PERSIST))
|
if (!input.hasKeyword(Keyword.PERSIST))
|
||||||
return false;
|
return false;
|
||||||
return input.getCounters(CounterType.M1M1) <= amount;
|
return input.getCounters(m1m1) <= amount;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!persist.isEmpty()) {
|
if (!persist.isEmpty()) {
|
||||||
filtered = persist;
|
filtered = persist;
|
||||||
}
|
}
|
||||||
} else if (CounterType.M1M1.equals(type)) {
|
} else if (m1m1.equals(type)) {
|
||||||
final CardCollection undying = CardLists.filter(filtered, new Predicate<Card>() {
|
final CardCollection undying = CardLists.filter(filtered, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Card input) {
|
public boolean apply(Card input) {
|
||||||
if (!input.hasKeyword(Keyword.UNDYING))
|
if (!input.hasKeyword(Keyword.UNDYING))
|
||||||
return false;
|
return false;
|
||||||
return input.getCounters(CounterType.P1P1) <= amount && input.getNetToughness() > amount;
|
return input.getCounters(p1p1) <= amount && input.getNetToughness() > amount;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -987,8 +1021,8 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
if (e instanceof Card) {
|
if (e instanceof Card) {
|
||||||
Card c = (Card) e;
|
Card c = (Card) e;
|
||||||
if (c.getController().isOpponentOf(ai)) {
|
if (c.getController().isOpponentOf(ai)) {
|
||||||
if (options.contains(CounterType.M1M1) && !c.hasKeyword(Keyword.UNDYING)) {
|
if (options.contains(CounterType.get(CounterEnumType.M1M1)) && !c.hasKeyword(Keyword.UNDYING)) {
|
||||||
return CounterType.M1M1;
|
return CounterType.get(CounterEnumType.M1M1);
|
||||||
}
|
}
|
||||||
for (CounterType type : options) {
|
for (CounterType type : options) {
|
||||||
if (ComputerUtil.isNegativeCounter(type, c)) {
|
if (ComputerUtil.isNegativeCounter(type, c)) {
|
||||||
@@ -1005,12 +1039,12 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
} else if (e instanceof Player) {
|
} else if (e instanceof Player) {
|
||||||
Player p = (Player) e;
|
Player p = (Player) e;
|
||||||
if (p.isOpponentOf(ai)) {
|
if (p.isOpponentOf(ai)) {
|
||||||
if (options.contains(CounterType.POISON)) {
|
if (options.contains(CounterType.get(CounterEnumType.POISON))) {
|
||||||
return CounterType.POISON;
|
return CounterType.get(CounterEnumType.POISON);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (options.contains(CounterType.EXPERIENCE)) {
|
if (options.contains(CounterType.get(CounterEnumType.EXPERIENCE))) {
|
||||||
return CounterType.EXPERIENCE;
|
return CounterType.get(CounterEnumType.EXPERIENCE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
|||||||
//Check for cards that could profit from the ability
|
//Check for cards that could profit from the ability
|
||||||
PhaseHandler phase = ai.getGame().getPhaseHandler();
|
PhaseHandler phase = ai.getGame().getPhaseHandler();
|
||||||
if (type.equals("P1P1") && sa.isAbility() && source.isCreature()
|
if (type.equals("P1P1") && sa.isAbility() && source.isCreature()
|
||||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
|
&& sa.getPayCosts().hasTapCost()
|
||||||
&& sa instanceof AbilitySub
|
&& sa instanceof AbilitySub
|
||||||
&& (!phase.getNextTurn().equals(ai)
|
&& (!phase.getNextTurn().equals(ai)
|
||||||
|| phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
|
|| phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (sa.hasParam("CounterType")) {
|
if (sa.hasParam("CounterType")) {
|
||||||
// currently only Jhoira's Timebug
|
// currently only Jhoira's Timebug
|
||||||
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
|
final CounterType type = CounterType.getType(sa.getParam("CounterType"));
|
||||||
|
|
||||||
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounter(type, amount));
|
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounter(type, amount));
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||||
CardCollectionView depthsList = CardLists.filter(countersList,
|
CardCollectionView depthsList = CardLists.filter(countersList,
|
||||||
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterType.ICE));
|
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterEnumType.ICE));
|
||||||
|
|
||||||
if (!depthsList.isEmpty()) {
|
if (!depthsList.isEmpty()) {
|
||||||
sa.getTargets().add(depthsList.getFirst());
|
sa.getTargets().add(depthsList.getFirst());
|
||||||
@@ -113,7 +113,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
CardCollection planeswalkerList = CardLists.filter(
|
CardCollection planeswalkerList = CardLists.filter(
|
||||||
CardLists.filterControlledBy(countersList, ai.getOpponents()),
|
CardLists.filterControlledBy(countersList, ai.getOpponents()),
|
||||||
CardPredicates.Presets.PLANESWALKERS,
|
CardPredicates.Presets.PLANESWALKERS,
|
||||||
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
|
CardPredicates.hasLessCounter(CounterEnumType.LOYALTY, amount));
|
||||||
|
|
||||||
if (!planeswalkerList.isEmpty()) {
|
if (!planeswalkerList.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
||||||
@@ -123,7 +123,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
// do as M1M1 part
|
// do as M1M1 part
|
||||||
CardCollection aiList = CardLists.filterControlledBy(countersList, ai);
|
CardCollection aiList = CardLists.filterControlledBy(countersList, ai);
|
||||||
|
|
||||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
|
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterEnumType.M1M1));
|
||||||
|
|
||||||
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, Keyword.PERSIST);
|
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, Keyword.PERSIST);
|
||||||
if (!aiPersistList.isEmpty()) {
|
if (!aiPersistList.isEmpty()) {
|
||||||
@@ -136,7 +136,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do as P1P1 part
|
// do as P1P1 part
|
||||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
|
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterEnumType.P1P1));
|
||||||
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, Keyword.UNDYING);
|
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, Keyword.UNDYING);
|
||||||
|
|
||||||
if (!aiUndyingList.isEmpty()) {
|
if (!aiUndyingList.isEmpty()) {
|
||||||
@@ -199,18 +199,18 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
Card tgt = (Card) params.get("Target");
|
Card tgt = (Card) params.get("Target");
|
||||||
|
|
||||||
// planeswalker has high priority for loyalty counters
|
// planeswalker has high priority for loyalty counters
|
||||||
if (tgt.isPlaneswalker() && options.contains(CounterType.LOYALTY)) {
|
if (tgt.isPlaneswalker() && options.contains(CounterType.get(CounterEnumType.LOYALTY))) {
|
||||||
return CounterType.LOYALTY;
|
return CounterType.get(CounterEnumType.LOYALTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tgt.getController().isOpponentOf(ai)) {
|
if (tgt.getController().isOpponentOf(ai)) {
|
||||||
// creatures with BaseToughness below or equal zero might be
|
// creatures with BaseToughness below or equal zero might be
|
||||||
// killed if their counters are removed
|
// killed if their counters are removed
|
||||||
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
|
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
|
||||||
if (options.contains(CounterType.P1P1)) {
|
if (options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
||||||
return CounterType.P1P1;
|
return CounterType.get(CounterEnumType.P1P1);
|
||||||
} else if (options.contains(CounterType.M1M1)) {
|
} else if (options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
||||||
return CounterType.M1M1;
|
return CounterType.get(CounterEnumType.M1M1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,14 +222,14 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// this counters are treat first to be removed
|
// this counters are treat first to be removed
|
||||||
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.ICE)) {
|
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.get(CounterEnumType.ICE))) {
|
||||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||||
return CounterType.ICE;
|
return CounterType.get(CounterEnumType.ICE);
|
||||||
}
|
}
|
||||||
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.P1P1)) {
|
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
||||||
return CounterType.P1P1;
|
return CounterType.get(CounterEnumType.P1P1);
|
||||||
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.M1M1)) {
|
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
||||||
return CounterType.M1M1;
|
return CounterType.get(CounterEnumType.M1M1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallback logic, select positive counter to add more
|
// fallback logic, select positive counter to add more
|
||||||
@@ -262,19 +262,19 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||||
|
|
||||||
if (tgt.getController().isOpponentOf(ai)) {
|
if (tgt.getController().isOpponentOf(ai)) {
|
||||||
if (type.equals(CounterType.LOYALTY) && tgt.isPlaneswalker()) {
|
if (type.is(CounterEnumType.LOYALTY) && tgt.isPlaneswalker()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ComputerUtil.isNegativeCounter(type, tgt);
|
return ComputerUtil.isNegativeCounter(type, tgt);
|
||||||
} else {
|
} else {
|
||||||
if (type.equals(CounterType.ICE) && "Dark Depths".equals(tgt.getName())) {
|
if (type.is(CounterEnumType.ICE) && "Dark Depths".equals(tgt.getName())) {
|
||||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (type.equals(CounterType.M1M1) && tgt.hasKeyword(Keyword.PERSIST)) {
|
} else if (type.is(CounterEnumType.M1M1) && tgt.hasKeyword(Keyword.PERSIST)) {
|
||||||
return false;
|
return false;
|
||||||
} else if (type.equals(CounterType.P1P1) && tgt.hasKeyword(Keyword.UNDYING)) {
|
} else if (type.is(CounterEnumType.P1P1) && tgt.hasKeyword(Keyword.UNDYING)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class CountersRemoveAi extends SpellAbilityAi {
|
public class CountersRemoveAi extends SpellAbilityAi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayWithoutRestrict(final Player ai, final SpellAbility sa) {
|
||||||
|
if ("Always".equals(sa.getParam("AILogic"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.canPlayWithoutRestrict(ai, sa);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
@@ -74,7 +82,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!type.matches("Any") && !type.matches("All")) {
|
if (!type.matches("Any") && !type.matches("All")) {
|
||||||
final int currCounters = sa.getHostCard().getCounters(CounterType.valueOf(type));
|
final int currCounters = sa.getHostCard().getCounters(CounterType.getType(type));
|
||||||
if (currCounters < 1) {
|
if (currCounters < 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -111,7 +119,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||||
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||||
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
||||||
CardPredicates.hasCounter(CounterType.ICE, 3));
|
CardPredicates.hasCounter(CounterEnumType.ICE, 3));
|
||||||
|
|
||||||
if (!depthsList.isEmpty()) {
|
if (!depthsList.isEmpty()) {
|
||||||
sa.getTargets().add(depthsList.getFirst());
|
sa.getTargets().add(depthsList.getFirst());
|
||||||
@@ -124,7 +132,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
|
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
|
||||||
|
|
||||||
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS,
|
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS,
|
||||||
CardPredicates.hasCounter(CounterType.LOYALTY, 5));
|
CardPredicates.hasCounter(CounterEnumType.LOYALTY, 5));
|
||||||
|
|
||||||
if (!planeswalkerList.isEmpty()) {
|
if (!planeswalkerList.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
||||||
@@ -151,11 +159,11 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||||
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||||
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
||||||
CardPredicates.hasCounter(CounterType.ICE));
|
CardPredicates.hasCounter(CounterEnumType.ICE));
|
||||||
|
|
||||||
if (!depthsList.isEmpty()) {
|
if (!depthsList.isEmpty()) {
|
||||||
Card depth = depthsList.getFirst();
|
Card depth = depthsList.getFirst();
|
||||||
int ice = depth.getCounters(CounterType.ICE);
|
int ice = depth.getCounters(CounterEnumType.ICE);
|
||||||
if (amount >= ice) {
|
if (amount >= ice) {
|
||||||
sa.getTargets().add(depth);
|
sa.getTargets().add(depth);
|
||||||
if (xPay) {
|
if (xPay) {
|
||||||
@@ -172,7 +180,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
CardCollection planeswalkerList = CardLists.filter(list,
|
CardCollection planeswalkerList = CardLists.filter(list,
|
||||||
Predicates.and(CardPredicates.Presets.PLANESWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
|
Predicates.and(CardPredicates.Presets.PLANESWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
|
||||||
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
|
CardPredicates.hasLessCounter(CounterEnumType.LOYALTY, amount));
|
||||||
|
|
||||||
if (!planeswalkerList.isEmpty()) {
|
if (!planeswalkerList.isEmpty()) {
|
||||||
Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
|
Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
|
||||||
@@ -188,7 +196,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
// do as M1M1 part
|
// do as M1M1 part
|
||||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||||
|
|
||||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
|
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterEnumType.M1M1));
|
||||||
|
|
||||||
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, Keyword.PERSIST);
|
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, Keyword.PERSIST);
|
||||||
if (!aiPersistList.isEmpty()) {
|
if (!aiPersistList.isEmpty()) {
|
||||||
@@ -201,7 +209,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do as P1P1 part
|
// do as P1P1 part
|
||||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasLessCounter(CounterType.P1P1, amount));
|
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasLessCounter(CounterEnumType.P1P1, amount));
|
||||||
CardCollection aiUndyingList = CardLists.getKeyword(aiP1P1List, Keyword.UNDYING);
|
CardCollection aiUndyingList = CardLists.getKeyword(aiP1P1List, Keyword.UNDYING);
|
||||||
|
|
||||||
if (!aiUndyingList.isEmpty()) {
|
if (!aiUndyingList.isEmpty()) {
|
||||||
@@ -212,7 +220,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
// remove P1P1 counters from opposing creatures
|
// remove P1P1 counters from opposing creatures
|
||||||
CardCollection oppP1P1List = CardLists.filter(list,
|
CardCollection oppP1P1List = CardLists.filter(list,
|
||||||
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
|
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
|
||||||
CardPredicates.hasCounter(CounterType.P1P1));
|
CardPredicates.hasCounter(CounterEnumType.P1P1));
|
||||||
if (!oppP1P1List.isEmpty()) {
|
if (!oppP1P1List.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(oppP1P1List));
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(oppP1P1List));
|
||||||
return true;
|
return true;
|
||||||
@@ -236,7 +244,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
// no special amount for that one yet
|
// no special amount for that one yet
|
||||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||||
aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1, amount));
|
aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterEnumType.M1M1, amount));
|
||||||
|
|
||||||
CardCollection aiPersist = CardLists.getKeyword(aiList, Keyword.PERSIST);
|
CardCollection aiPersist = CardLists.getKeyword(aiList, Keyword.PERSIST);
|
||||||
if (!aiPersist.isEmpty()) {
|
if (!aiPersist.isEmpty()) {
|
||||||
@@ -255,7 +263,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
// no special amount for that one yet
|
// no special amount for that one yet
|
||||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
|
|
||||||
list = CardLists.filter(list, CardPredicates.hasCounter(CounterType.P1P1, amount));
|
list = CardLists.filter(list, CardPredicates.hasCounter(CounterEnumType.P1P1, amount));
|
||||||
|
|
||||||
// currently only logic for Bloodcrazed Hoplite, but add logic for
|
// currently only logic for Bloodcrazed Hoplite, but add logic for
|
||||||
// targeting ai creatures too
|
// targeting ai creatures too
|
||||||
@@ -301,12 +309,12 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterType.TIME, amount));
|
CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterEnumType.TIME, amount));
|
||||||
|
|
||||||
if (!timeList.isEmpty()) {
|
if (!timeList.isEmpty()) {
|
||||||
Card best = ComputerUtilCard.getBestAI(timeList);
|
Card best = ComputerUtilCard.getBestAI(timeList);
|
||||||
|
|
||||||
int timeCount = best.getCounters(CounterType.TIME);
|
int timeCount = best.getCounters(CounterEnumType.TIME);
|
||||||
sa.getTargets().add(best);
|
sa.getTargets().add(best);
|
||||||
if (xPay) {
|
if (xPay) {
|
||||||
source.setSVar("PayX", Integer.toString(timeCount));
|
source.setSVar("PayX", Integer.toString(timeCount));
|
||||||
@@ -327,7 +335,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
CardCollection outlastCreats = CardLists.filter(list, CardPredicates.hasKeyword(Keyword.OUTLAST));
|
CardCollection outlastCreats = CardLists.filter(list, CardPredicates.hasKeyword(Keyword.OUTLAST));
|
||||||
if (!outlastCreats.isEmpty()) {
|
if (!outlastCreats.isEmpty()) {
|
||||||
// outlast cards often benefit from having +1/+1 counters, try not to remove last one
|
// outlast cards often benefit from having +1/+1 counters, try not to remove last one
|
||||||
CardCollection betterTargets = CardLists.filter(outlastCreats, CardPredicates.hasCounter(CounterType.P1P1, 2));
|
CardCollection betterTargets = CardLists.filter(outlastCreats, CardPredicates.hasCounter(CounterEnumType.P1P1, 2));
|
||||||
|
|
||||||
if (!betterTargets.isEmpty()) {
|
if (!betterTargets.isEmpty()) {
|
||||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(betterTargets));
|
sa.getTargets().add(ComputerUtilCard.getWorstAI(betterTargets));
|
||||||
@@ -369,8 +377,8 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
if (targetCard.getController().isOpponentOf(player)) {
|
if (targetCard.getController().isOpponentOf(player)) {
|
||||||
return !ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
|
return !ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
|
||||||
} else {
|
} else {
|
||||||
if (targetCard.hasKeyword(Keyword.UNDYING) && type == CounterType.P1P1
|
if (targetCard.hasKeyword(Keyword.UNDYING) && type.is(CounterEnumType.P1P1)
|
||||||
&& targetCard.getCounters(CounterType.P1P1) >= max) {
|
&& targetCard.getCounters(CounterEnumType.P1P1) >= max) {
|
||||||
return max;
|
return max;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,9 +387,9 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
} else if (target instanceof Player) {
|
} else if (target instanceof Player) {
|
||||||
Player targetPlayer = (Player) target;
|
Player targetPlayer = (Player) target;
|
||||||
if (targetPlayer.isOpponentOf(player)) {
|
if (targetPlayer.isOpponentOf(player)) {
|
||||||
return !type.equals(CounterType.POISON) ? max : min;
|
return !type.equals(CounterEnumType.POISON) ? max : min;
|
||||||
} else {
|
} else {
|
||||||
return type.equals(CounterType.POISON) ? max : min;
|
return type.equals(CounterEnumType.POISON) ? max : min;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,7 +415,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
if (targetCard.getController().isOpponentOf(ai)) {
|
if (targetCard.getController().isOpponentOf(ai)) {
|
||||||
// if its a Planeswalker try to remove Loyality first
|
// if its a Planeswalker try to remove Loyality first
|
||||||
if (targetCard.isPlaneswalker()) {
|
if (targetCard.isPlaneswalker()) {
|
||||||
return CounterType.LOYALTY;
|
return CounterType.get(CounterEnumType.LOYALTY);
|
||||||
}
|
}
|
||||||
for (CounterType type : options) {
|
for (CounterType type : options) {
|
||||||
if (!ComputerUtil.isNegativeCounter(type, targetCard)) {
|
if (!ComputerUtil.isNegativeCounter(type, targetCard)) {
|
||||||
@@ -415,10 +423,10 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (options.contains(CounterType.M1M1) && targetCard.hasKeyword(Keyword.PERSIST)) {
|
if (options.contains(CounterType.get(CounterEnumType.M1M1)) && targetCard.hasKeyword(Keyword.PERSIST)) {
|
||||||
return CounterType.M1M1;
|
return CounterType.get(CounterEnumType.M1M1);
|
||||||
} else if (options.contains(CounterType.P1P1) && targetCard.hasKeyword(Keyword.UNDYING)) {
|
} else if (options.contains(CounterType.get(CounterEnumType.P1P1)) && targetCard.hasKeyword(Keyword.UNDYING)) {
|
||||||
return CounterType.P1P1;
|
return CounterType.get(CounterEnumType.P1P1);
|
||||||
}
|
}
|
||||||
for (CounterType type : options) {
|
for (CounterType type : options) {
|
||||||
if (ComputerUtil.isNegativeCounter(type, targetCard)) {
|
if (ComputerUtil.isNegativeCounter(type, targetCard)) {
|
||||||
@@ -430,13 +438,13 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
Player targetPlayer = (Player) target;
|
Player targetPlayer = (Player) target;
|
||||||
if (targetPlayer.isOpponentOf(ai)) {
|
if (targetPlayer.isOpponentOf(ai)) {
|
||||||
for (CounterType type : options) {
|
for (CounterType type : options) {
|
||||||
if (!type.equals(CounterType.POISON)) {
|
if (!type.equals(CounterEnumType.POISON)) {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (CounterType type : options) {
|
for (CounterType type : options) {
|
||||||
if (type.equals(CounterType.POISON)) {
|
if (type.equals(CounterEnumType.POISON)) {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ 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.CounterEnumType;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
@@ -50,10 +50,9 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
}
|
}
|
||||||
if (damage.equals("ChosenX")) {
|
if (damage.equals("ChosenX")) {
|
||||||
x = source.getCounters(CounterType.LOYALTY);
|
x = source.getCounters(CounterEnumType.LOYALTY);
|
||||||
}
|
}
|
||||||
if (x == -1) {
|
if (x == -1) {
|
||||||
Player bestOpp = determineOppToKill(ai, sa, source, dmg);
|
|
||||||
if (determineOppToKill(ai, sa, source, dmg) != null) {
|
if (determineOppToKill(ai, sa, source, dmg) != null) {
|
||||||
// we already know we can kill a player, so go for it
|
// we already know we can kill a player, so go for it
|
||||||
return true;
|
return true;
|
||||||
@@ -138,7 +137,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int minGain = 200; // The minimum gain in destroyed creatures
|
int minGain = 200; // The minimum gain in destroyed creatures
|
||||||
if (sa.getPayCosts() != null && sa.getPayCosts().isReusuableResource()) {
|
if (sa.getPayCosts().isReusuableResource()) {
|
||||||
if (computerList.isEmpty()) {
|
if (computerList.isEmpty()) {
|
||||||
minGain = 10; // nothing to lose
|
minGain = 10; // nothing to lose
|
||||||
// no creatures to lose and player can be damaged
|
// no creatures to lose and player can be damaged
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.base.Predicates;
|
||||||
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.*;
|
import forge.ai.*;
|
||||||
@@ -47,7 +48,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$ChosenNumber")) {
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$ChosenNumber")) {
|
||||||
int energy = ai.getCounters(CounterType.ENERGY);
|
int energy = ai.getCounters(CounterEnumType.ENERGY);
|
||||||
for (SpellAbility s : source.getSpellAbilities()) {
|
for (SpellAbility s : source.getSpellAbilities()) {
|
||||||
if ("PayEnergy".equals(s.getParam("AILogic"))) {
|
if ("PayEnergy".equals(s.getParam("AILogic"))) {
|
||||||
energy += AbilityUtils.calculateAmount(source, s.getParam("CounterNum"), sa);
|
energy += AbilityUtils.calculateAmount(source, s.getParam("CounterNum"), sa);
|
||||||
@@ -164,7 +165,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
List<Card> wolves = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), "Creature.Wolf+untapped+YouCtrl+Other", ai, source);
|
List<Card> wolves = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), "Creature.Wolf+untapped+YouCtrl+Other", ai, source);
|
||||||
dmg = Aggregates.sum(wolves, CardPredicates.Accessors.fnGetNetPower);
|
dmg = Aggregates.sum(wolves, CardPredicates.Accessors.fnGetNetPower);
|
||||||
} else if ("Triskelion".equals(logic)) {
|
} else if ("Triskelion".equals(logic)) {
|
||||||
final int n = source.getCounters(CounterType.P1P1);
|
final int n = source.getCounters(CounterEnumType.P1P1);
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||||
/*
|
/*
|
||||||
@@ -197,7 +198,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sourceName.equals("Sorin, Grim Nemesis")) {
|
if (sourceName.equals("Sorin, Grim Nemesis")) {
|
||||||
int loyalty = source.getCounters(CounterType.LOYALTY);
|
int loyalty = source.getCounters(CounterEnumType.LOYALTY);
|
||||||
for (; loyalty > 0; loyalty--) {
|
for (; loyalty > 0; loyalty--) {
|
||||||
if (this.damageTargetAI(ai, sa, loyalty, false)) {
|
if (this.damageTargetAI(ai, sa, loyalty, false)) {
|
||||||
dmg = ComputerUtilCombat.getEnoughDamageToKill(sa.getTargetCard(), loyalty, source, false, false);
|
dmg = ComputerUtilCombat.getEnoughDamageToKill(sa.getTargetCard(), loyalty, source, false, false);
|
||||||
@@ -284,7 +285,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("XCountersDamage".equals(logic) && sa.getPayCosts() != null) {
|
if ("XCountersDamage".equals(logic)) {
|
||||||
// Check to ensure that we have enough counters to remove per the defined PayX
|
// Check to ensure that we have enough counters to remove per the defined PayX
|
||||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||||
if (part instanceof CostRemoveCounter) {
|
if (part instanceof CostRemoveCounter) {
|
||||||
@@ -444,11 +445,11 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
// As of right now, ranks planeswalkers by their Current Loyalty * 10 + Big buff if close to "Ultimate"
|
// As of right now, ranks planeswalkers by their Current Loyalty * 10 + Big buff if close to "Ultimate"
|
||||||
int bestScore = 0;
|
int bestScore = 0;
|
||||||
for (Card pw : pws) {
|
for (Card pw : pws) {
|
||||||
int curLoyalty = pw.getCounters(CounterType.LOYALTY);
|
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
|
||||||
int pwScore = curLoyalty * 10;
|
int pwScore = curLoyalty * 10;
|
||||||
|
|
||||||
for (SpellAbility sa : pw.getSpellAbilities()) {
|
for (SpellAbility sa : pw.getSpellAbilities()) {
|
||||||
if (sa.hasParam("Ultimate") && sa.getPayCosts() != null) {
|
if (sa.hasParam("Ultimate")) {
|
||||||
Integer loyaltyCost = 0;
|
Integer loyaltyCost = 0;
|
||||||
CostRemoveCounter remLoyalty = sa.getPayCosts().getCostPartByType(CostRemoveCounter.class);
|
CostRemoveCounter remLoyalty = sa.getPayCosts().getCostPartByType(CostRemoveCounter.class);
|
||||||
if (remLoyalty != null) {
|
if (remLoyalty != null) {
|
||||||
@@ -472,6 +473,22 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
return bestTgt;
|
return bestTgt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Card getWorstPlaneswalkerToDamage(final List<Card> pws) {
|
||||||
|
Card bestTgt = null;
|
||||||
|
|
||||||
|
int bestScore = Integer.MAX_VALUE;
|
||||||
|
for (Card pw : pws) {
|
||||||
|
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
|
||||||
|
|
||||||
|
if (curLoyalty < bestScore) {
|
||||||
|
bestScore = curLoyalty;
|
||||||
|
bestTgt = pw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestTgt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private List<Card> getTargetableCards(Player ai, SpellAbility sa, Player pl, TargetRestrictions tgt, Player activator, Card source, Game game) {
|
private List<Card> getTargetableCards(Player ai, SpellAbility sa, Player pl, TargetRestrictions tgt, Player activator, Card source, Game game) {
|
||||||
List<Card> hPlay = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), activator, source, sa);
|
List<Card> hPlay = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), activator, source, sa);
|
||||||
@@ -556,6 +573,13 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
sa.getTargets().add(enemy);
|
sa.getTargets().add(enemy);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
} else if ("DamageAfterPutCounter".equals(logic)
|
||||||
|
&& sa.getParent() != null
|
||||||
|
&& "P1P1".equals(sa.getParent().getParam("CounterType"))) {
|
||||||
|
// assuming the SA parent is of PutCounter type. Perhaps it's possible to predict counter multipliers here somehow?
|
||||||
|
final String amountStr = sa.getParent().getParamOrDefault("CounterNum", "1");
|
||||||
|
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||||
|
dmg += amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssumeAtLeastOneTarget is used for cards with funky targeting implementation like Fight with Fire which would
|
// AssumeAtLeastOneTarget is used for cards with funky targeting implementation like Fight with Fire which would
|
||||||
@@ -566,7 +590,10 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
|
|
||||||
immediately |= ComputerUtil.playImmediately(ai, sa);
|
immediately |= ComputerUtil.playImmediately(ai, sa);
|
||||||
|
|
||||||
|
if (!(sa.getParent() != null && sa.getParent().isTargetNumberValid())) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
}
|
||||||
|
|
||||||
// target loop
|
// target loop
|
||||||
TargetChoices tcs = sa.getTargets();
|
TargetChoices tcs = sa.getTargets();
|
||||||
|
|
||||||
@@ -767,8 +794,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
||||||
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|
||||||
|| ("PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai))
|
|| ("PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai))
|
||||||
|| sa.getPayCosts() == null || immediately
|
|| immediately || shouldTgtP(ai, sa, dmg, noPrevention)) &&
|
||||||
|| this.shouldTgtP(ai, sa, dmg, noPrevention)) &&
|
|
||||||
(!avoidTargetP(ai, sa))) {
|
(!avoidTargetP(ai, sa))) {
|
||||||
tcs.add(enemy);
|
tcs.add(enemy);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
@@ -785,7 +811,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
// If the trigger is mandatory, gotta choose my own stuff now
|
// If the trigger is mandatory, gotta choose my own stuff now
|
||||||
return this.damageChooseRequiredTargets(ai, sa, tgt, dmg, mandatory);
|
return this.damageChooseRequiredTargets(ai, sa, tgt, dmg);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO is this good enough? for up to amounts?
|
// TODO is this good enough? for up to amounts?
|
||||||
@@ -862,12 +888,9 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
* a {@link forge.game.spellability.TargetRestrictions} object.
|
* a {@link forge.game.spellability.TargetRestrictions} object.
|
||||||
* @param dmg
|
* @param dmg
|
||||||
* a int.
|
* a int.
|
||||||
* @param mandatory
|
|
||||||
* a boolean.
|
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
private boolean damageChooseRequiredTargets(final Player ai, final SpellAbility sa, final TargetRestrictions tgt, final int dmg,
|
private boolean damageChooseRequiredTargets(final Player ai, final SpellAbility sa, final TargetRestrictions tgt, final int dmg) {
|
||||||
final boolean mandatory) {
|
|
||||||
// this is for Triggered targets that are mandatory
|
// this is for Triggered targets that are mandatory
|
||||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||||
@@ -875,7 +898,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
|
|
||||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||||
if (tgt.canTgtPlaneswalker()) {
|
if (tgt.canTgtPlaneswalker()) {
|
||||||
final Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, ai, mandatory);
|
final Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, ai, true);
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
@@ -888,7 +911,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
|
|
||||||
// TODO: This currently also catches planeswalkers that can be killed (still necessary? Or can be removed?)
|
// TODO: This currently also catches planeswalkers that can be killed (still necessary? Or can be removed?)
|
||||||
if (tgt.canTgtCreature()) {
|
if (tgt.canTgtCreature()) {
|
||||||
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, ai, mandatory);
|
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, ai, true);
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
@@ -909,6 +932,32 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See if there's an indestructible target that can be used
|
||||||
|
CardCollection indestructible = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
|
||||||
|
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.Presets.PLANESWALKERS, CardPredicates.hasKeyword(Keyword.INDESTRUCTIBLE), CardPredicates.isTargetableBy(sa)));
|
||||||
|
|
||||||
|
if (!indestructible.isEmpty()) {
|
||||||
|
Card c = ComputerUtilCard.getWorstPermanentAI(indestructible, false, false, false, false);
|
||||||
|
sa.getTargets().add(c);
|
||||||
|
if (divided) {
|
||||||
|
tgt.addDividedAllocation(c, dmg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (tgt.canTgtPlaneswalker()) {
|
||||||
|
// Second pass for planeswalkers: choose AI's worst planeswalker
|
||||||
|
final Card c = getWorstPlaneswalkerToDamage(CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Predicates.and(CardPredicates.Presets.PLANESWALKERS), CardPredicates.isTargetableBy(sa)));
|
||||||
|
if (c != null) {
|
||||||
|
sa.getTargets().add(c);
|
||||||
|
if (divided) {
|
||||||
|
tgt.addDividedAllocation(c, dmg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (sa.canTarget(ai)) {
|
if (sa.canTarget(ai)) {
|
||||||
if (sa.getTargets().add(ai)) {
|
if (sa.getTargets().add(ai)) {
|
||||||
if (divided) {
|
if (divided) {
|
||||||
@@ -1076,8 +1125,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// currently works only with cards that don't have additional costs (only mana is supported)
|
// currently works only with cards that don't have additional costs (only mana is supported)
|
||||||
if (ab.getPayCosts() != null
|
if (ab.getPayCosts().hasNoManaCost() || ab.getPayCosts().hasOnlySpecificCostType(CostPartMana.class)) {
|
||||||
&& (ab.getPayCosts().hasNoManaCost() || ab.getPayCosts().hasOnlySpecificCostType(CostPartMana.class))) {
|
|
||||||
String dmgDef = "0";
|
String dmgDef = "0";
|
||||||
if (ab.getApi() == ApiType.DealDamage) {
|
if (ab.getApi() == ApiType.DealDamage) {
|
||||||
dmgDef = ab.getParamOrDefault("NumDmg", "0");
|
dmgDef = ab.getParamOrDefault("NumDmg", "0");
|
||||||
@@ -1101,7 +1149,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: should it also check restrictions for targeting players?
|
// FIXME: should it also check restrictions for targeting players?
|
||||||
ManaCost costSa = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : ManaCost.NO_COST;
|
ManaCost costSa = sa.getPayCosts().getTotalMana();
|
||||||
ManaCost costAb = ab.getPayCosts().getTotalMana(); // checked for null above
|
ManaCost costAb = ab.getPayCosts().getTotalMana(); // checked for null above
|
||||||
ManaCost total = ManaCost.combine(costSa, costAb);
|
ManaCost total = ManaCost.combine(costSa, costAb);
|
||||||
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));
|
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
|
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
|
||||||
} else if (logic != null && logic.startsWith("MinLoyalty.")) {
|
} else if (logic != null && logic.startsWith("MinLoyalty.")) {
|
||||||
int minLoyalty = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
|
int minLoyalty = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
|
||||||
if (source.getCounters(CounterType.LOYALTY) < minLoyalty) {
|
if (source.getCounters(CounterEnumType.LOYALTY) < minLoyalty) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if ("Polymorph".equals(logic)) {
|
} else if ("Polymorph".equals(logic)) {
|
||||||
@@ -161,7 +161,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//Check for undying
|
//Check for undying
|
||||||
return (!c.hasKeyword(Keyword.UNDYING) || c.getCounters(CounterType.P1P1) > 0);
|
return (!c.hasKeyword(Keyword.UNDYING) || c.getCounters(CounterEnumType.P1P1) > 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -231,7 +231,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
if ("Pongify".equals(logic)) {
|
if ("Pongify".equals(logic)) {
|
||||||
final Card token = TokenAi.spawnToken(choice.getController(), sa.getSubAbility());
|
final Card token = TokenAi.spawnToken(choice.getController(), sa.getSubAbility());
|
||||||
if (token == null) {
|
if (token == null || !token.isCreature() || token.getNetToughness() < 1) {
|
||||||
return true; // becomes Terminate
|
return true; // becomes Terminate
|
||||||
} else {
|
} else {
|
||||||
if (source.getGame().getPhaseHandler().getPhase()
|
if (source.getGame().getPhaseHandler().getPhase()
|
||||||
@@ -256,7 +256,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
//option to hold removal instead only applies for single targeted removal
|
//option to hold removal instead only applies for single targeted removal
|
||||||
if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
|
if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
|
||||||
if (!ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
if (choice == null || !ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,6 +277,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
SpellAbility sp = aura.getFirstSpellAbility();
|
SpellAbility sp = aura.getFirstSpellAbility();
|
||||||
if (sp != null && "GainControl".equals(sp.getParam("AILogic"))
|
if (sp != null && "GainControl".equals(sp.getParam("AILogic"))
|
||||||
&& aura.getController() != ai && sa.canTarget(aura)) {
|
&& aura.getController() != ai && sa.canTarget(aura)) {
|
||||||
|
list.remove(choice);
|
||||||
choice = aura;
|
choice = aura;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
@@ -130,7 +134,7 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer) {
|
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer, Map<String, Object> params) {
|
||||||
if ("DigForCreature".equals(sa.getParam("AILogic"))) {
|
if ("DigForCreature".equals(sa.getParam("AILogic"))) {
|
||||||
Card bestChoice = ComputerUtilCard.getBestCreatureAI(valid);
|
Card bestChoice = ComputerUtilCard.getBestCreatureAI(valid);
|
||||||
if (bestChoice == null) {
|
if (bestChoice == null) {
|
||||||
@@ -157,6 +161,15 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||||
|
// an opponent choose a card from
|
||||||
|
return Iterables.getFirst(options, null);
|
||||||
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import forge.game.ability.ApiType;
|
|||||||
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.card.CounterEnumType;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.cost.*;
|
import forge.game.cost.*;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
@@ -262,7 +263,6 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
// Draw up to max hand size but leave at least 3 in library
|
// Draw up to max hand size but leave at least 3 in library
|
||||||
numCards = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
|
numCards = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
|
||||||
|
|
||||||
if (sa.getPayCosts() != null) {
|
|
||||||
if (sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) {
|
if (sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) {
|
||||||
// [Necrologia, Pay X Life : Draw X Cards]
|
// [Necrologia, Pay X Life : Draw X Cards]
|
||||||
// Don't draw more than what's "safe" and don't risk a near death experience
|
// Don't draw more than what's "safe" and don't risk a near death experience
|
||||||
@@ -279,7 +279,6 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sa.setSVar("ChosenX", Integer.toString(numCards));
|
sa.setSVar("ChosenX", Integer.toString(numCards));
|
||||||
source.setSVar("ChosenX", Integer.toString(numCards));
|
source.setSVar("ChosenX", Integer.toString(numCards));
|
||||||
@@ -350,7 +349,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
// try to make opponent lose to poison
|
// try to make opponent lose to poison
|
||||||
// currently only Caress of Phyrexia
|
// currently only Caress of Phyrexia
|
||||||
if (getPoison != null && oppA.canReceiveCounters(CounterType.POISON)) {
|
if (getPoison != null && oppA.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
if (oppA.getPoisonCounters() + numCards > 9) {
|
if (oppA.getPoisonCounters() + numCards > 9) {
|
||||||
sa.getTargets().add(oppA);
|
sa.getTargets().add(oppA);
|
||||||
return true;
|
return true;
|
||||||
@@ -394,7 +393,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getPoison != null && ai.canReceiveCounters(CounterType.POISON)) {
|
if (getPoison != null && ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
if (numCards + ai.getPoisonCounters() >= 8) {
|
if (numCards + ai.getPoisonCounters() >= 8) {
|
||||||
aiTarget = false;
|
aiTarget = false;
|
||||||
}
|
}
|
||||||
@@ -453,7 +452,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ally would lose because of poison
|
// ally would lose because of poison
|
||||||
if (getPoison != null && ally.canReceiveCounters(CounterType.POISON)) {
|
if (getPoison != null && ally.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
if (ally.getPoisonCounters() + numCards > 9) {
|
if (ally.getPoisonCounters() + numCards > 9) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -514,6 +513,10 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
if (!mandatory && !willPayCosts(ai, sa, sa.getPayCosts(), sa.getHostCard())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return targetAI(ai, sa, mandatory);
|
return targetAI(ai, sa, mandatory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import forge.card.mana.ManaCost;
|
|||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
@@ -112,7 +113,7 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
} else if (logic.equals("SpellCopy")) {
|
} else if (logic.equals("SpellCopy")) {
|
||||||
// fetch Instant or Sorcery and AI has reason to play this turn
|
// fetch Instant or Sorcery and AI has reason to play this turn
|
||||||
// does not try to get itself
|
// does not try to get itself
|
||||||
final ManaCost costSa = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : ManaCost.NO_COST;
|
final ManaCost costSa = sa.getPayCosts().getTotalMana();
|
||||||
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
|
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
@@ -134,7 +135,7 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(ab);
|
AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(ab);
|
||||||
// see if we can pay both for this spell and for the Effect spell we're considering
|
// see if we can pay both for this spell and for the Effect spell we're considering
|
||||||
if (decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2) {
|
if (decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2) {
|
||||||
ManaCost costAb = ab.getPayCosts() != null ? ab.getPayCosts().getTotalMana() : ManaCost.NO_COST;
|
ManaCost costAb = ab.getPayCosts().getTotalMana();
|
||||||
ManaCost total = ManaCost.combine(costSa, costAb);
|
ManaCost total = ManaCost.combine(costSa, costAb);
|
||||||
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));
|
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));
|
||||||
// can we pay both costs?
|
// can we pay both costs?
|
||||||
@@ -307,6 +308,16 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) {
|
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (logic.equals("Bribe")) {
|
||||||
|
Card host = sa.getHostCard();
|
||||||
|
Combat combat = game.getCombat();
|
||||||
|
if (combat != null && combat.isAttacking(host, ai) && !combat.isBlocked(host)
|
||||||
|
&& game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
|
&& !AiCardMemory.isRememberedCard(ai, host, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN)) {
|
||||||
|
AiCardMemory.rememberCard(ai, host, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN); // ideally needs once per combat or something
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
} else { //no AILogic
|
} else { //no AILogic
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
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.base.Predicate;
|
||||||
|
|
||||||
@@ -84,7 +85,7 @@ public final class EncodeAi extends SpellAbilityAi {
|
|||||||
* 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, Map<String, Object> params) {
|
||||||
return chooseCard(ai, options, isOptional);
|
return chooseCard(ai, options, isOptional);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
||||||
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
||||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||||
|
if (humCreatures.isEmpty())
|
||||||
|
return false; //prevent IndexOutOfBoundsException on MOJHOSTO variant
|
||||||
|
|
||||||
// assumes the triggered card belongs to the ai
|
// assumes the triggered card belongs to the ai
|
||||||
if (sa.hasParam("Defined")) {
|
if (sa.hasParam("Defined")) {
|
||||||
|
|||||||
27
forge-ai/src/main/java/forge/ai/ability/InvestigateAi.java
Normal file
27
forge-ai/src/main/java/forge/ai/ability/InvestigateAi.java
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.phase.PhaseHandler;
|
||||||
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
|
public class InvestigateAi 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) {
|
||||||
|
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
||||||
|
|
||||||
|
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
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.CounterEnumType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
|
||||||
@@ -23,7 +25,7 @@ public class LegendaryRuleAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
|
|
||||||
@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, Map<String, Object> params) {
|
||||||
// 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();
|
||||||
@@ -38,16 +40,16 @@ public class LegendaryRuleAi extends SpellAbilityAi {
|
|||||||
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(CounterEnumType.ICE) < best.getCounters(CounterEnumType.ICE)) {
|
||||||
best = c;
|
best = c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return best;
|
return best;
|
||||||
} else if (firstOption.getCounters(CounterType.KI) > 0) {
|
} else if (firstOption.getCounters(CounterEnumType.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(CounterEnumType.KI) > best.getCounters(CounterEnumType.KI)) {
|
||||||
best = c;
|
best = c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
if (lifeCritical
|
if (lifeCritical
|
||||||
&& sa.isAbility()
|
&& sa.isAbility()
|
||||||
&& sa.getHostCard() != null && sa.getHostCard().isCreature()
|
&& sa.getHostCard() != null && sa.getHostCard().isCreature()
|
||||||
&& sa.getPayCosts() != null
|
|
||||||
&& (sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class) || sa.getPayCosts().hasSpecificCostType(CostSacrifice.class))) {
|
&& (sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class) || sa.getPayCosts().hasSpecificCostType(CostSacrifice.class))) {
|
||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
SpellAbility saTop = game.getStack().peekAbility();
|
SpellAbility saTop = game.getStack().peekAbility();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import forge.ai.ComputerUtilMana;
|
|||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterEnumType;
|
||||||
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;
|
||||||
@@ -130,7 +130,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sourceName.equals("Eternity Vessel")
|
if (sourceName.equals("Eternity Vessel")
|
||||||
&& (opponent.isCardInPlay("Vampire Hexmage") || (source.getCounters(CounterType.CHARGE) == 0))) {
|
&& (opponent.isCardInPlay("Vampire Hexmage") || (source.getCounters(CounterEnumType.CHARGE) == 0))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ public class ManaEffectAi extends SpellAbilityAi {
|
|||||||
return true; // handled elsewhere, does not meet the standard requirements
|
return true; // handled elsewhere, does not meet the standard requirements
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.getPayCosts() != null && sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
|
return sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
|
||||||
&& sa.getSubAbility() == null && ComputerUtil.playImmediately(ai, sa);
|
&& sa.getSubAbility() == null && ComputerUtil.playImmediately(ai, sa);
|
||||||
// return super.checkApiLogic(ai, sa);
|
// return super.checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
@@ -119,8 +119,8 @@ public class ManaEffectAi extends SpellAbilityAi {
|
|||||||
int numCounters = 0;
|
int numCounters = 0;
|
||||||
int manaSurplus = 0;
|
int manaSurplus = 0;
|
||||||
if ("XChoice".equals(host.getSVar("X"))
|
if ("XChoice".equals(host.getSVar("X"))
|
||||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
|
&& sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
|
||||||
CounterType ctrType = CounterType.KI; // Petalmane Baku
|
CounterType ctrType = CounterType.get(CounterEnumType.KI); // Petalmane Baku
|
||||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||||
if (part instanceof CostRemoveCounter) {
|
if (part instanceof CostRemoveCounter) {
|
||||||
ctrType = ((CostRemoveCounter)part).counter;
|
ctrType = ((CostRemoveCounter)part).counter;
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
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.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtilMana;
|
||||||
|
import forge.ai.SpecialCardAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -24,6 +19,11 @@ 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 java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class MillAi extends SpellAbilityAi {
|
public class MillAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -38,6 +38,8 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
} else if (aiLogic.equals("LilianaMill")) {
|
} else if (aiLogic.equals("LilianaMill")) {
|
||||||
// Only mill if a "Raise Dead" target is available, in case of control decks with few creatures
|
// Only mill if a "Raise Dead" target is available, in case of control decks with few creatures
|
||||||
return CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES).size() >= 1;
|
return CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES).size() >= 1;
|
||||||
|
} else if (aiLogic.equals("Rebirth")) {
|
||||||
|
return ai.getLife() <= 8;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -194,6 +196,10 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
|
if ("TimmerianFiends".equals(sa.getParam("AILogic"))) {
|
||||||
|
return SpecialCardAi.TimmerianFiends.consider(player, sa);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -12,14 +12,13 @@ import forge.game.card.CardPredicates;
|
|||||||
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.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseHandler;
|
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class MustBlockAi extends SpellAbilityAi {
|
public class MustBlockAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -28,7 +27,6 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Game game = aiPlayer.getGame();
|
final Game game = aiPlayer.getGame();
|
||||||
final Combat combat = game.getCombat();
|
final Combat combat = game.getCombat();
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
|
||||||
final boolean onlyLethal = !"AllowNonLethal".equals(sa.getParam("AILogic"));
|
final boolean onlyLethal = !"AllowNonLethal".equals(sa.getParam("AILogic"));
|
||||||
|
|
||||||
if (combat == null || !combat.isAttacking(source)) {
|
if (combat == null || !combat.isAttacking(source)) {
|
||||||
@@ -39,7 +37,6 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
|
||||||
final List<Card> list = determineGoodBlockers(source, aiPlayer, combat.getDefenderPlayerByAttacker(source), sa, onlyLethal,false);
|
final List<Card> list = determineGoodBlockers(source, aiPlayer, combat.getDefenderPlayerByAttacker(source), sa, onlyLethal,false);
|
||||||
|
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
@@ -69,7 +66,6 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
|
||||||
|
|
||||||
// only use on creatures that can attack
|
// only use on creatures that can attack
|
||||||
if (!ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
if (!ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
@@ -94,7 +90,7 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
boolean chance = false;
|
boolean chance = false;
|
||||||
|
|
||||||
if (abTgt != null) {
|
if (sa.usesTargeting()) {
|
||||||
final List<Card> list = determineGoodBlockers(definedAttacker, ai, ai.getWeakestOpponent(), sa, true,true);
|
final List<Card> list = determineGoodBlockers(definedAttacker, ai, ai.getWeakestOpponent(), sa, true,true);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -119,6 +115,9 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
sa.getTargets().add(blocker);
|
sa.getTargets().add(blocker);
|
||||||
chance = true;
|
chance = true;
|
||||||
|
} else if (sa.hasParam("Choices")) {
|
||||||
|
// currently choice is attacked player
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -126,16 +125,9 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
return chance;
|
return chance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Card> determineGoodBlockers(final Card attacker, final Player ai, Player defender, SpellAbility sa,
|
private List<Card> determineBlockerFromList(final Card attacker, final Player ai, Iterable<Card> options, SpellAbility sa,
|
||||||
final boolean onlyLethal, final boolean testTapped) {
|
final boolean onlyLethal, final boolean testTapped) {
|
||||||
final Card source = sa.getHostCard();
|
List<Card> list = CardLists.filter(options, new Predicate<Card>() {
|
||||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
|
||||||
|
|
||||||
List<Card> list = Lists.newArrayList();
|
|
||||||
list = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
|
||||||
list = CardLists.getTargetableCards(list, sa);
|
|
||||||
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source, sa);
|
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
boolean tapped = c.isTapped();
|
boolean tapped = c.isTapped();
|
||||||
@@ -161,4 +153,40 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Card> determineGoodBlockers(final Card attacker, final Player ai, Player defender, SpellAbility sa,
|
||||||
|
final boolean onlyLethal, final boolean testTapped) {
|
||||||
|
|
||||||
|
List<Card> list = Lists.newArrayList();
|
||||||
|
list = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
||||||
|
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
|
}
|
||||||
|
return determineBlockerFromList(attacker, ai, list, sa, onlyLethal, testTapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||||
|
Player targetedPlayer, Map<String, Object> params) {
|
||||||
|
final Card host = sa.getHostCard();
|
||||||
|
|
||||||
|
Card attacker = host;
|
||||||
|
|
||||||
|
if (sa.hasParam("DefinedAttacker")) {
|
||||||
|
List<Card> attackers = AbilityUtils.getDefinedCards(host, sa.getParam("DefinedAttacker"), sa);
|
||||||
|
attacker = Iterables.getFirst(attackers, null);
|
||||||
|
}
|
||||||
|
if (attacker == null) {
|
||||||
|
return Iterables.getFirst(options, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Card> better = determineBlockerFromList(attacker, ai, options, sa, false, false);
|
||||||
|
|
||||||
|
if (!better.isEmpty()) {
|
||||||
|
return Iterables.getFirst(options, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Iterables.getFirst(options, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import forge.game.zone.ZoneType;
|
|||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class PlayAi extends SpellAbilityAi {
|
public class PlayAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -84,11 +85,11 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"));
|
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"));
|
||||||
} else if (logic.startsWith("NeedsChosenCard")) {
|
} else if (logic.startsWith("NeedsChosenCard")) {
|
||||||
int minCMC = 0;
|
int minCMC = 0;
|
||||||
if (sa.getPayCosts() != null && sa.getPayCosts().getCostMana() != null) {
|
if (sa.getPayCosts().getCostMana() != null) {
|
||||||
minCMC = sa.getPayCosts().getCostMana().getMana().getCMC();
|
minCMC = sa.getPayCosts().getTotalMana().getCMC();
|
||||||
}
|
}
|
||||||
validOpts = CardLists.filter(validOpts, CardPredicates.greaterCMC(minCMC));
|
validOpts = CardLists.filter(validOpts, CardPredicates.greaterCMC(minCMC));
|
||||||
return chooseSingleCard(ai, sa, validOpts, sa.hasParam("Optional"), null) != null;
|
return chooseSingleCard(ai, sa, validOpts, sa.hasParam("Optional"), null, null) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source != null && source.hasKeyword(Keyword.HIDEAWAY) && source.hasRemembered()) {
|
if (source != null && source.hasKeyword(Keyword.HIDEAWAY) && source.hasRemembered()) {
|
||||||
@@ -142,8 +143,7 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options,
|
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options,
|
||||||
final boolean isOptional,
|
final boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
Player targetedPlayer) {
|
|
||||||
List<Card> tgtCards = CardLists.filter(options, new Predicate<Card>() {
|
List<Card> tgtCards = CardLists.filter(options, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
@@ -156,9 +156,7 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
if (sa.hasParam("WithoutManaCost")) {
|
if (sa.hasParam("WithoutManaCost")) {
|
||||||
// Try to avoid casting instants and sorceries with X in their cost, since X will be assumed to be 0.
|
// Try to avoid casting instants and sorceries with X in their cost, since X will be assumed to be 0.
|
||||||
if (!(spell instanceof SpellPermanent)) {
|
if (!(spell instanceof SpellPermanent)) {
|
||||||
if (spell.getPayCosts() != null
|
if (spell.getPayCosts().getTotalMana().countX() > 0) {
|
||||||
&& spell.getPayCosts().getCostMana() != null
|
|
||||||
&& spell.getPayCosts().getCostMana().getMana().countX() > 0) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.google.common.base.Predicate;
|
|||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
@@ -59,7 +60,7 @@ public class PoisonAi extends SpellAbilityAi {
|
|||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
return tgtPlayer(ai, sa, mandatory);
|
return tgtPlayer(ai, sa, mandatory);
|
||||||
} else if (mandatory || !ai.canReceiveCounters(CounterType.POISON)) {
|
} else if (mandatory || !ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
// mandatory or ai is uneffected
|
// mandatory or ai is uneffected
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -92,7 +93,7 @@ public class PoisonAi extends SpellAbilityAi {
|
|||||||
public boolean apply(Player input) {
|
public boolean apply(Player input) {
|
||||||
if (input.cantLose()) {
|
if (input.cantLose()) {
|
||||||
return false;
|
return false;
|
||||||
} else if (!input.canReceiveCounters(CounterType.POISON)) {
|
} else if (!input.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -113,7 +114,7 @@ public class PoisonAi extends SpellAbilityAi {
|
|||||||
if (tgts.isEmpty()) {
|
if (tgts.isEmpty()) {
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
// AI is uneffected
|
// AI is uneffected
|
||||||
if (ai.canBeTargetedBy(sa) && ai.canReceiveCounters(CounterType.POISON)) {
|
if (ai.canBeTargetedBy(sa) && ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -127,7 +128,7 @@ public class PoisonAi extends SpellAbilityAi {
|
|||||||
if (input.cantLose()) {
|
if (input.cantLose()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return !input.canReceiveCounters(CounterType.POISON);
|
return !input.canReceiveCounters(CounterType.get(CounterEnumType.POISON));
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
|
||||||
import forge.ai.AiAttackController;
|
import forge.ai.*;
|
||||||
import forge.ai.ComputerUtil;
|
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.ComputerUtilCombat;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
@@ -206,7 +202,7 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
if (game.getStack().isEmpty()) {
|
if (game.getStack().isEmpty()) {
|
||||||
// If the cost is tapping, don't activate before declare
|
// If the cost is tapping, don't activate before declare
|
||||||
// attack/block
|
// attack/block
|
||||||
if ((sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) {
|
if (sa.getPayCosts().hasTapCost()) {
|
||||||
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
&& game.getPhaseHandler().isPlayerTurn(ai)) {
|
&& game.getPhaseHandler().isPlayerTurn(ai)) {
|
||||||
list.remove(sa.getHostCard());
|
list.remove(sa.getHostCard());
|
||||||
@@ -221,6 +217,11 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
// Don't target cards that will die.
|
// Don't target cards that will die.
|
||||||
list = ComputerUtil.getSafeTargets(ai, sa, list);
|
list = ComputerUtil.getSafeTargets(ai, sa, list);
|
||||||
|
|
||||||
|
// Don't target self if the cost includes sacrificing itself
|
||||||
|
if (ComputerUtilCost.isSacrificeSelfCost(sa.getPayCosts())) {
|
||||||
|
list.remove(source);
|
||||||
|
}
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return mandatory && protectMandatoryTarget(ai, sa, mandatory);
|
return mandatory && protectMandatoryTarget(ai, sa, mandatory);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String counterType = moveSA.getParam("CounterType");
|
final String counterType = moveSA.getParam("CounterType");
|
||||||
final CounterType cType = "Any".equals(counterType) ? null : CounterType.valueOf(counterType);
|
final CounterType cType = "Any".equals(counterType) ? null : CounterType.getType(counterType);
|
||||||
|
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
if (ph.inCombat() && ph.getPlayerTurn().isOpponentOf(ai)) {
|
if (ph.inCombat() && ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||||
@@ -185,7 +185,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
// cant use substract on Copy
|
// cant use substract on Copy
|
||||||
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
|
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
|
||||||
|
|
||||||
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
if (CounterEnumType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
||||||
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|
||||||
|| card.isToken();
|
|| card.isToken();
|
||||||
}
|
}
|
||||||
@@ -235,7 +235,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
// cant use substract on Copy
|
// cant use substract on Copy
|
||||||
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
|
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
|
||||||
|
|
||||||
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
if (CounterEnumType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
||||||
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|
||||||
|| card.isToken();
|
|| card.isToken();
|
||||||
}
|
}
|
||||||
@@ -402,7 +402,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
|
|
||||||
if ("DebuffForXCounters".equals(sa.getParam("AILogic")) && sa.getTargetCard() != null) {
|
if ("DebuffForXCounters".equals(sa.getParam("AILogic")) && sa.getTargetCard() != null) {
|
||||||
// e.g. Skullmane Baku
|
// e.g. Skullmane Baku
|
||||||
CounterType ctrType = CounterType.KI;
|
CounterType ctrType = CounterType.get(CounterEnumType.KI);
|
||||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||||
if (part instanceof CostRemoveCounter) {
|
if (part instanceof CostRemoveCounter) {
|
||||||
ctrType = ((CostRemoveCounter)part).counter;
|
ctrType = ((CostRemoveCounter)part).counter;
|
||||||
@@ -515,7 +515,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
if (game.getStack().isEmpty()) {
|
if (game.getStack().isEmpty()) {
|
||||||
// If the cost is tapping, don't activate before declare
|
// If the cost is tapping, don't activate before declare
|
||||||
// attack/block
|
// attack/block
|
||||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
if (sa.getPayCosts().hasTapCost()) {
|
||||||
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
&& game.getPhaseHandler().isPlayerTurn(ai)) {
|
&& game.getPhaseHandler().isPlayerTurn(ai)) {
|
||||||
list.remove(sa.getHostCard());
|
list.remove(sa.getHostCard());
|
||||||
@@ -730,7 +730,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : "";
|
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : "";
|
||||||
|
|
||||||
if (numDefense.equals("-X") && sa.getSVar("X").equals("Count$ChosenNumber")) {
|
if (numDefense.equals("-X") && sa.getSVar("X").equals("Count$ChosenNumber")) {
|
||||||
int energy = ai.getCounters(CounterType.ENERGY);
|
int energy = ai.getCounters(CounterEnumType.ENERGY);
|
||||||
for (SpellAbility s : source.getSpellAbilities()) {
|
for (SpellAbility s : source.getSpellAbilities()) {
|
||||||
if ("PayEnergy".equals(s.getParam("AILogic"))) {
|
if ("PayEnergy".equals(s.getParam("AILogic"))) {
|
||||||
energy += AbilityUtils.calculateAmount(source, s.getParam("CounterNum"), sa);
|
energy += AbilityUtils.calculateAmount(source, s.getParam("CounterNum"), sa);
|
||||||
@@ -860,7 +860,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
final boolean isInfect = source.hasKeyword(Keyword.INFECT); // Flesh-Eater Imp
|
final boolean isInfect = source.hasKeyword(Keyword.INFECT); // Flesh-Eater Imp
|
||||||
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
||||||
|
|
||||||
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.POISON)) {
|
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -976,7 +976,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
final boolean isInfect = source.hasKeyword(Keyword.INFECT);
|
final boolean isInfect = source.hasKeyword(Keyword.INFECT);
|
||||||
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
||||||
|
|
||||||
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.POISON)) {
|
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||||
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import forge.game.combat.CombatUtil;
|
|||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
|
import forge.game.phase.Untap;
|
||||||
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;
|
||||||
@@ -93,7 +94,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
List<Card> attackers = CardLists.filter(ai.getCreaturesInPlay(), new Predicate<Card>() {
|
List<Card> attackers = CardLists.filter(ai.getCreaturesInPlay(), new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
if (c.equals(sa.getHostCard()) && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
|
if (c.equals(sa.getHostCard()) && sa.getPayCosts().hasTapCost()
|
||||||
&& (combat == null || !combat.isAttacking(c))) {
|
&& (combat == null || !combat.isAttacking(c))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -111,7 +112,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
List<Card> attackers = CardLists.filter(ai.getCreaturesInPlay(), new Predicate<Card>() {
|
List<Card> attackers = CardLists.filter(ai.getCreaturesInPlay(), new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
if (c.equals(sa.getHostCard()) && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
|
if (c.equals(sa.getHostCard()) && sa.getPayCosts().hasTapCost()
|
||||||
&& (combat == null || !combat.isAttacking(c))) {
|
&& (combat == null || !combat.isAttacking(c))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -129,6 +130,9 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
// target needs to be a creature, controlled by the player which is attacked
|
// target needs to be a creature, controlled by the player which is attacked
|
||||||
return !sa.getHostCard().isTapped() || (combat != null && combat.isAttacking(sa.getHostCard())
|
return !sa.getHostCard().isTapped() || (combat != null && combat.isAttacking(sa.getHostCard())
|
||||||
&& card.getController().equals(combat.getDefenderPlayerByAttacker(sa.getHostCard())));
|
&& card.getController().equals(combat.getDefenderPlayerByAttacker(sa.getHostCard())));
|
||||||
|
} else if (keyword.endsWith("This card doesn't untap during your next untap step.")) {
|
||||||
|
return !ph.getPhase().isBefore(PhaseType.MAIN2) && !card.isUntapped() && ph.isPlayerTurn(ai)
|
||||||
|
&& Untap.canUntap(card);
|
||||||
} else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.")
|
} else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.")
|
||||||
|| keyword.endsWith("Prevent all damage that would be dealt by CARDNAME.")) {
|
|| keyword.endsWith("Prevent all damage that would be dealt by CARDNAME.")) {
|
||||||
if (ph.isPlayerTurn(ai) && (!(CombatUtil.canBlock(card) || combat != null && combat.isBlocking(card))
|
if (ph.isPlayerTurn(ai) && (!(CombatUtil.canBlock(card) || combat != null && combat.isBlocking(card))
|
||||||
@@ -507,7 +511,8 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
for (final String keyword : keywords) {
|
for (final String keyword : keywords) {
|
||||||
// since most keywords are combat relevant check for those that are
|
// since most keywords are combat relevant check for those that are
|
||||||
// not
|
// not
|
||||||
if (keyword.endsWith("Shroud") || keyword.endsWith("Hexproof")) {
|
if (keyword.endsWith("This card doesn't untap during your next untap step.")
|
||||||
|
|| keyword.endsWith("Shroud") || keyword.endsWith("Hexproof")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class RearrangeTopOfLibraryAi extends SpellAbilityAi {
|
|||||||
final PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
final PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
if (source.isPermanent() && sa.getRestrictions().isInstantSpeed() && sa.getPayCosts() != null
|
if (source.isPermanent() && sa.getRestrictions().isInstantSpeed()
|
||||||
&& (sa.getPayCosts().hasTapCost() || sa.getPayCosts().hasManaCost())) {
|
&& (sa.getPayCosts().hasTapCost() || sa.getPayCosts().hasManaCost())) {
|
||||||
// If it has an associated cost, try to only do this before own turn
|
// If it has an associated cost, try to only do this before own turn
|
||||||
if (!(ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer)) {
|
if (!(ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer)) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpecialCardAi;
|
import forge.ai.SpecialCardAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
@@ -16,6 +15,7 @@ import forge.game.zone.ZoneType;
|
|||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
public class RepeatEachAi extends SpellAbilityAi {
|
public class RepeatEachAi extends SpellAbilityAi {
|
||||||
@@ -47,21 +47,6 @@ public class RepeatEachAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if ("GainControlOwns".equals(logic)) {
|
|
||||||
List<Card> list = CardLists.filter(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(final Card crd) {
|
|
||||||
return crd.isCreature() && !crd.getController().equals(crd.getOwner());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (list.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (final Card c : list) {
|
|
||||||
if (aiPlayer.equals(c.getController())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ("OpponentHasCreatures".equals(logic)) {
|
} else if ("OpponentHasCreatures".equals(logic)) {
|
||||||
for (Player opp : aiPlayer.getOpponents()) {
|
for (Player opp : aiPlayer.getOpponents()) {
|
||||||
if (!opp.getCreaturesInPlay().isEmpty()){
|
if (!opp.getCreaturesInPlay().isEmpty()){
|
||||||
@@ -118,7 +103,7 @@ public class RepeatEachAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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, Map<String, Object> params) {
|
||||||
return ComputerUtilCard.getBestCreatureAI(options);
|
return ComputerUtilCard.getBestCreatureAI(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
final boolean destroy = sa.hasParam("Destroy");
|
final boolean destroy = sa.hasParam("Destroy");
|
||||||
|
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = ai.getWeakestOpponent();
|
||||||
|
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
if (!opp.canBeTargetedBy(sa)) {
|
||||||
@@ -74,8 +75,16 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
num = (num == null) ? "1" : num;
|
num = (num == null) ? "1" : num;
|
||||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), num, sa);
|
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), num, sa);
|
||||||
|
|
||||||
List<Card> list =
|
List<Card> list = null;
|
||||||
CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
try {
|
||||||
|
list = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (list == null)
|
||||||
|
return false;
|
||||||
|
}//prevent NPE on MoJhoSto
|
||||||
|
|
||||||
for (Card c : list) {
|
for (Card c : list) {
|
||||||
if (c.hasSVar("SacMe") && Integer.parseInt(c.getSVar("SacMe")) > 3) {
|
if (c.hasSVar("SacMe") && Integer.parseInt(c.getSVar("SacMe")) > 3) {
|
||||||
return false;
|
return false;
|
||||||
@@ -131,15 +140,31 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
amount = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), amount);
|
amount = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Card> humanList =
|
List<Card> humanList = null;
|
||||||
CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
try {
|
||||||
|
humanList = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (humanList == null)
|
||||||
|
return false;
|
||||||
|
}//prevent NPE on MoJhoSto
|
||||||
|
|
||||||
// Since all of the cards have AI:RemoveDeck:All, I enabled 1 for 1
|
// Since all of the cards have AI:RemoveDeck:All, I enabled 1 for 1
|
||||||
// (or X for X) trades for special decks
|
// (or X for X) trades for special decks
|
||||||
return humanList.size() >= amount;
|
return humanList.size() >= amount;
|
||||||
} else if (defined.equals("You")) {
|
} else if (defined.equals("You")) {
|
||||||
List<Card> computerList =
|
|
||||||
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
List<Card> computerList = null;
|
||||||
|
try {
|
||||||
|
computerList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (computerList == null)
|
||||||
|
return false;
|
||||||
|
}//prevent NPE on MoJhoSto
|
||||||
|
|
||||||
for (Card c : computerList) {
|
for (Card c : computerList) {
|
||||||
if (c.hasSVar("SacMe") || ComputerUtilCard.evaluateCreature(c) <= 135) {
|
if (c.hasSVar("SacMe") || ComputerUtilCard.evaluateCreature(c) <= 135) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -46,13 +46,11 @@ public class ScryAi extends SpellAbilityAi {
|
|||||||
// and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
|
// and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
|
||||||
// try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
|
// try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
|
||||||
// even if there's no mana cost.
|
// even if there's no mana cost.
|
||||||
if (sa.getPayCosts() != null) {
|
|
||||||
if (sa.getPayCosts().hasTapCost()
|
if (sa.getPayCosts().hasTapCost()
|
||||||
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
|
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
|
||||||
&& !SpellAbilityAi.isSorcerySpeed(sa)) {
|
&& !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||||
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
|
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// AI logic to scry in Main 1 if there is no better option, otherwise scry at opponent's EOT
|
// AI logic to scry in Main 1 if there is no better option, otherwise scry at opponent's EOT
|
||||||
// (e.g. Glimmer of Genius)
|
// (e.g. Glimmer of Genius)
|
||||||
@@ -76,8 +74,7 @@ public class ScryAi extends SpellAbilityAi {
|
|||||||
boolean hasSomethingElse = false;
|
boolean hasSomethingElse = false;
|
||||||
for (Card c : CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.not(CardPredicates.Presets.LANDS))) {
|
for (Card c : CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.not(CardPredicates.Presets.LANDS))) {
|
||||||
for (SpellAbility ab : c.getAllSpellAbilities()) {
|
for (SpellAbility ab : c.getAllSpellAbilities()) {
|
||||||
if (ab.getPayCosts() != null
|
if (ab.getPayCosts().hasManaCost()
|
||||||
&& ab.getPayCosts().hasManaCost()
|
|
||||||
&& ComputerUtilMana.hasEnoughManaSourcesToCast(ab, ai)) {
|
&& ComputerUtilMana.hasEnoughManaSourcesToCast(ab, ai)) {
|
||||||
// TODO: currently looks for non-Scry cards, can most certainly be made smarter.
|
// TODO: currently looks for non-Scry cards, can most certainly be made smarter.
|
||||||
if (ab.getApi() != ApiType.Scry) {
|
if (ab.getApi() != ApiType.Scry) {
|
||||||
@@ -102,7 +99,7 @@ public class ScryAi extends SpellAbilityAi {
|
|||||||
} else if ("BrainJar".equals(aiLogic)) {
|
} else if ("BrainJar".equals(aiLogic)) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
int counterNum = source.getCounters(CounterType.CHARGE);
|
int counterNum = source.getCounters(CounterEnumType.CHARGE);
|
||||||
// no need for logic
|
// no need for logic
|
||||||
if (counterNum == 0) {
|
if (counterNum == 0) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ public class SetStateAi extends SpellAbilityAi {
|
|||||||
final Card othercard = aiPlayer.getCardsIn(ZoneType.Battlefield, other.getName()).getFirst();
|
final Card othercard = aiPlayer.getCardsIn(ZoneType.Battlefield, other.getName()).getFirst();
|
||||||
|
|
||||||
// for legendary KI counter creatures
|
// for legendary KI counter creatures
|
||||||
if (othercard.getCounters(CounterType.KI) >= source.getCounters(CounterType.KI)) {
|
if (othercard.getCounters(CounterEnumType.KI) >= source.getCounters(CounterEnumType.KI)) {
|
||||||
// if the other legendary is useless try to replace it
|
// if the other legendary is useless try to replace it
|
||||||
return ComputerUtilCard.isUselessCreature(aiPlayer, othercard);
|
return ComputerUtilCard.isUselessCreature(aiPlayer, othercard);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,13 +47,11 @@ public class SurveilAi extends SpellAbilityAi {
|
|||||||
// and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
|
// and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
|
||||||
// try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
|
// try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
|
||||||
// even if there's no mana cost.
|
// even if there's no mana cost.
|
||||||
if (sa.getPayCosts() != null) {
|
|
||||||
if (sa.getPayCosts().hasTapCost()
|
if (sa.getPayCosts().hasTapCost()
|
||||||
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
|
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
|
||||||
&& !SpellAbilityAi.isSorcerySpeed(sa)) {
|
&& !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||||
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
|
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// in the player's turn Surveil should only be done in Main1 or in Upkeep if able
|
// in the player's turn Surveil should only be done in Main1 or in Upkeep if able
|
||||||
if (ph.isPlayerTurn(ai)) {
|
if (ph.isPlayerTurn(ai)) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package forge.ai.ability;
|
|||||||
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.CounterEnumType;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.cost.CostPart;
|
import forge.game.cost.CostPart;
|
||||||
@@ -68,7 +69,7 @@ public class TapAi extends TapAiBase {
|
|||||||
} else {
|
} else {
|
||||||
if ("TapForXCounters".equals(sa.getParam("AILogic"))) {
|
if ("TapForXCounters".equals(sa.getParam("AILogic"))) {
|
||||||
// e.g. Waxmane Baku
|
// e.g. Waxmane Baku
|
||||||
CounterType ctrType = CounterType.KI;
|
CounterType ctrType = CounterType.get(CounterEnumType.KI);
|
||||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||||
if (part instanceof CostRemoveCounter) {
|
if (part instanceof CostRemoveCounter) {
|
||||||
ctrType = ((CostRemoveCounter)part).counter;
|
ctrType = ((CostRemoveCounter)part).counter;
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||||
if (sa.isAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,7 +147,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||||
if (sa.isAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
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.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -25,16 +26,9 @@ import forge.game.player.PlayerActionConfirmMode;
|
|||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.trigger.Trigger;
|
|
||||||
import forge.game.trigger.TriggerHandler;
|
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.item.PaperToken;
|
|
||||||
import forge.util.MyRandom;
|
|
||||||
import forge.util.TextUtil;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import forge.util.MyRandom;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -45,35 +39,10 @@ import java.util.List;
|
|||||||
* @version $Id: AbilityFactoryToken.java 17656 2012-10-22 19:32:56Z Max mtg $
|
* @version $Id: AbilityFactoryToken.java 17656 2012-10-22 19:32:56Z Max mtg $
|
||||||
*/
|
*/
|
||||||
public class TokenAi extends SpellAbilityAi {
|
public class TokenAi extends SpellAbilityAi {
|
||||||
private String tokenAmount;
|
|
||||||
private String tokenPower;
|
|
||||||
private String tokenToughness;
|
|
||||||
|
|
||||||
private Card actualToken;
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Constructor for AbilityFactory_Token.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* a {@link forge.game.ability.AbilityFactory} object.
|
|
||||||
*/
|
|
||||||
private void readParameters(final SpellAbility mapParams) {
|
|
||||||
this.tokenAmount = mapParams.getParamOrDefault("TokenAmount", "1");
|
|
||||||
|
|
||||||
this.actualToken = TokenInfo.getProtoType(mapParams.getParam("TokenScript"), mapParams);
|
|
||||||
|
|
||||||
if (actualToken == null) {
|
|
||||||
this.tokenPower = mapParams.getParam("TokenPower");
|
|
||||||
this.tokenToughness = mapParams.getParam("TokenToughness");
|
|
||||||
} else {
|
|
||||||
this.tokenPower = actualToken.getBasePowerString();
|
|
||||||
this.tokenToughness = actualToken.getBaseToughnessString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||||
readParameters(sa); // remember to call this somewhere!
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
// Planeswalker-related flags
|
// Planeswalker-related flags
|
||||||
boolean pwMinus = false;
|
boolean pwMinus = false;
|
||||||
@@ -96,21 +65,23 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
String tokenAmount = sa.getParamOrDefault("TokenAmount", "1");
|
||||||
|
|
||||||
if (actualToken == null) {
|
Card actualToken = spawnToken(ai, sa);
|
||||||
actualToken = spawnToken(ai, sa);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actualToken == null) {
|
if (actualToken == null || actualToken.getNetToughness() < 1) {
|
||||||
final AbilitySub sub = sa.getSubAbility();
|
final AbilitySub sub = sa.getSubAbility();
|
||||||
// useful
|
// useful
|
||||||
// no token created
|
// no token created
|
||||||
return pwPlus || (sub != null && SpellApiToAi.Converter.get(sub.getApi()).chkAIDrawback(sub, ai)); // planeswalker plus ability or sub-ability is
|
return pwPlus || (sub != null && SpellApiToAi.Converter.get(sub.getApi()).chkAIDrawback(sub, ai)); // planeswalker plus ability or sub-ability is
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String tokenPower = sa.getParamOrDefault("TokenPower", actualToken.getBasePowerString());
|
||||||
|
String tokenToughness = sa.getParamOrDefault("TokenToughness", actualToken.getBaseToughnessString());
|
||||||
|
|
||||||
// X-cost spells
|
// X-cost spells
|
||||||
if (this.tokenAmount.equals("X") || (this.tokenToughness != null && this.tokenToughness.equals("X"))) {
|
if ("X".equals(tokenAmount) || "X".equals(tokenPower) || "X".equals(tokenToughness)) {
|
||||||
int x = AbilityUtils.calculateAmount(sa.getHostCard(), this.tokenAmount, sa);
|
int x = AbilityUtils.calculateAmount(sa.getHostCard(), tokenAmount, sa);
|
||||||
if (source.getSVar("X").equals("Count$Converge")) {
|
if (source.getSVar("X").equals("Count$Converge")) {
|
||||||
x = ComputerUtilMana.getConvergeCount(sa, ai);
|
x = ComputerUtilMana.getConvergeCount(sa, ai);
|
||||||
}
|
}
|
||||||
@@ -124,14 +95,14 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canInterruptSacrifice(ai, sa, actualToken)) {
|
if (canInterruptSacrifice(ai, sa, actualToken, tokenAmount)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean haste = this.actualToken.hasKeyword(Keyword.HASTE);
|
boolean haste = actualToken.hasKeyword(Keyword.HASTE);
|
||||||
boolean oneShot = sa.getSubAbility() != null
|
boolean oneShot = sa.getSubAbility() != null
|
||||||
&& sa.getSubAbility().getApi() == ApiType.DelayedTrigger;
|
&& sa.getSubAbility().getApi() == ApiType.DelayedTrigger;
|
||||||
boolean isCreature = this.actualToken.getType().isCreature();
|
boolean isCreature = actualToken.getType().isCreature();
|
||||||
|
|
||||||
// Don't generate tokens without haste before main 2 if possible
|
// Don't generate tokens without haste before main 2 if possible
|
||||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && ph.isPlayerTurn(ai) && !haste && !sa.hasParam("ActivationPhases")
|
if (ph.getPhase().isBefore(PhaseType.MAIN2) && ph.isPlayerTurn(ai) && !haste && !sa.hasParam("ActivationPhases")
|
||||||
@@ -166,9 +137,10 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false; // prevent infinite tokens?
|
return false; // prevent infinite tokens?
|
||||||
}
|
}
|
||||||
|
Card actualToken = spawnToken(ai, sa);
|
||||||
|
|
||||||
// Don't kill AIs Legendary tokens
|
// Don't kill AIs Legendary tokens
|
||||||
if (this.actualToken.getType().isLegendary() && ai.isCardInPlay(this.actualToken.getName())) {
|
if (actualToken.getType().isLegendary() && ai.isCardInPlay(actualToken.getName())) {
|
||||||
// TODO Check if Token is useless due to an aura or counters?
|
// TODO Check if Token is useless due to an aura or counters?
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -240,7 +212,7 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
/**
|
/**
|
||||||
* Checks if the token(s) can save a creature from a sacrifice effect
|
* Checks if the token(s) can save a creature from a sacrifice effect
|
||||||
*/
|
*/
|
||||||
private boolean canInterruptSacrifice(final Player ai, final SpellAbility sa, final Card token) {
|
private boolean canInterruptSacrifice(final Player ai, final SpellAbility sa, final Card token, final String tokenAmount) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
if (game.getStack().isEmpty()) {
|
if (game.getStack().isEmpty()) {
|
||||||
return false; // nothing to interrupt
|
return false; // nothing to interrupt
|
||||||
@@ -249,7 +221,7 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
if (topStack.getApi() != ApiType.Sacrifice) {
|
if (topStack.getApi() != ApiType.Sacrifice) {
|
||||||
return false; // not sacrifice effect
|
return false; // not sacrifice effect
|
||||||
}
|
}
|
||||||
final int nTokens = AbilityUtils.calculateAmount(sa.getHostCard(), this.tokenAmount, sa);
|
final int nTokens = AbilityUtils.calculateAmount(sa.getHostCard(), tokenAmount, sa);
|
||||||
final String valid = topStack.getParamOrDefault("SacValid", "Card.Self");
|
final String valid = topStack.getParamOrDefault("SacValid", "Card.Self");
|
||||||
String num = sa.getParam("Amount");
|
String num = sa.getParam("Amount");
|
||||||
num = (num == null) ? "1" : num;
|
num = (num == null) ? "1" : num;
|
||||||
@@ -271,7 +243,8 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
readParameters(sa);
|
String tokenAmount = sa.getParamOrDefault("TokenAmount", "1");
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
@@ -282,8 +255,12 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ("X".equals(this.tokenAmount) || "X".equals(this.tokenPower) || "X".equals(this.tokenToughness)) {
|
Card actualToken = spawnToken(ai, sa);
|
||||||
int x = AbilityUtils.calculateAmount(source, this.tokenAmount, sa);
|
String tokenPower = sa.getParamOrDefault("TokenPower", actualToken.getBasePowerString());
|
||||||
|
String tokenToughness = sa.getParamOrDefault("TokenToughness", actualToken.getBaseToughnessString());
|
||||||
|
|
||||||
|
if ("X".equals(tokenAmount) || "X".equals(tokenPower) || "X".equals(tokenToughness)) {
|
||||||
|
int x = AbilityUtils.calculateAmount(source, tokenAmount, sa);
|
||||||
if (source.getSVar("X").equals("Count$xPaid")) {
|
if (source.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||||
@@ -321,9 +298,7 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, Iterable<forge.game.player.Player> options)
|
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, Iterable<forge.game.player.Player> options)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||||
// TODO: AILogic
|
|
||||||
readParameters(sa); // remember to call this somewhere!
|
|
||||||
Combat combat = ai.getGame().getCombat();
|
Combat combat = ai.getGame().getCombat();
|
||||||
// TokenAttacking
|
// TokenAttacking
|
||||||
if (combat != null && sa.hasParam("TokenAttacking")) {
|
if (combat != null && sa.hasParam("TokenAttacking")) {
|
||||||
@@ -341,9 +316,7 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayerOrPlaneswalker(forge.game.player.Player, forge.card.spellability.SpellAbility, Iterable<forge.game.GameEntity> options)
|
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayerOrPlaneswalker(forge.game.player.Player, forge.card.spellability.SpellAbility, Iterable<forge.game.GameEntity> options)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options) {
|
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
|
||||||
// TODO: AILogic
|
|
||||||
readParameters(sa); // remember to call this somewhere!
|
|
||||||
Combat combat = ai.getGame().getCombat();
|
Combat combat = ai.getGame().getCombat();
|
||||||
// TokenAttacking
|
// TokenAttacking
|
||||||
if (combat != null && sa.hasParam("TokenAttacking")) {
|
if (combat != null && sa.hasParam("TokenAttacking")) {
|
||||||
@@ -374,154 +347,22 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
* @param sa Token SpellAbility
|
* @param sa Token SpellAbility
|
||||||
* @return token creature created by ability
|
* @return token creature created by ability
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
|
||||||
public static Card spawnToken(Player ai, SpellAbility sa) {
|
public static Card spawnToken(Player ai, SpellAbility sa) {
|
||||||
return spawnToken(ai, sa, false);
|
if (!sa.hasParam("TokenScript")) {
|
||||||
|
throw new RuntimeException("Spell Ability has no TokenScript: " + sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the token as a Card object.
|
|
||||||
* @param ai owner of the new token
|
|
||||||
* @param sa Token SpellAbility
|
|
||||||
* @param notNull if the token would not survive, still return it
|
|
||||||
* @return token creature created by ability
|
|
||||||
*/
|
|
||||||
// TODO Is this just completely copied from TokenEffect? Let's just call that thing
|
|
||||||
@Deprecated
|
|
||||||
public static Card spawnToken(Player ai, SpellAbility sa, boolean notNull) {
|
|
||||||
final Card host = sa.getHostCard();
|
|
||||||
|
|
||||||
Card result = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa);
|
Card result = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa);
|
||||||
|
|
||||||
if (result != null) {
|
if (result == null) {
|
||||||
result.setController(ai, 0);
|
throw new RuntimeException("don't find Token for TokenScript: " + sa.getParam("TokenScript"));
|
||||||
|
}
|
||||||
|
|
||||||
|
result.setOwner(ai);
|
||||||
|
|
||||||
|
// Apply static abilities
|
||||||
|
final Game game = ai.getGame();
|
||||||
|
ComputerUtilCard.applyStaticContPT(game, result, null);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] tokenKeywords = sa.hasParam("TokenKeywords") ? sa.getParam("TokenKeywords").split("<>") : new String[0];
|
|
||||||
String tokenPower = sa.getParam("TokenPower");
|
|
||||||
String tokenToughness = sa.getParam("TokenToughness");
|
|
||||||
String tokenName = sa.getParam("TokenName");
|
|
||||||
String[] tokenTypes = sa.getParam("TokenTypes").split(",");
|
|
||||||
StringBuilder cost = new StringBuilder();
|
|
||||||
String[] tokenColors = sa.getParam("TokenColors").split(",");
|
|
||||||
String tokenImage = sa.hasParam("TokenImage") ? PaperToken.makeTokenFileName(sa.getParam("TokenImage")) : "";
|
|
||||||
String[] tokenAbilities = sa.hasParam("TokenAbilities") ? sa.getParam("TokenAbilities").split(",") : null;
|
|
||||||
String[] tokenTriggers = sa.hasParam("TokenTriggers") ? sa.getParam("TokenTriggers").split(",") : null;
|
|
||||||
String[] tokenSVars = sa.hasParam("TokenSVars") ? sa.getParam("TokenSVars").split(",") : null;
|
|
||||||
String[] tokenStaticAbilities = sa.hasParam("TokenStaticAbilities") ? sa.getParam("TokenStaticAbilities").split(",") : null;
|
|
||||||
String[] tokenHiddenKeywords = sa.hasParam("TokenHiddenKeywords") ? sa.getParam("TokenHiddenKeywords").split("&") : null;
|
|
||||||
final String[] substitutedColors = Arrays.copyOf(tokenColors, tokenColors.length);
|
|
||||||
for (int i = 0; i < substitutedColors.length; i++) {
|
|
||||||
if (substitutedColors[i].equals("ChosenColor")) {
|
|
||||||
// this currently only supports 1 chosen color
|
|
||||||
substitutedColors[i] = host.getChosenColor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StringBuilder colorDesc = new StringBuilder();
|
|
||||||
for (final String col : substitutedColors) {
|
|
||||||
if (col.equalsIgnoreCase("White")) {
|
|
||||||
colorDesc.append("W ");
|
|
||||||
} else if (col.equalsIgnoreCase("Blue")) {
|
|
||||||
colorDesc.append("U ");
|
|
||||||
} else if (col.equalsIgnoreCase("Black")) {
|
|
||||||
colorDesc.append("B ");
|
|
||||||
} else if (col.equalsIgnoreCase("Red")) {
|
|
||||||
colorDesc.append("R ");
|
|
||||||
} else if (col.equalsIgnoreCase("Green")) {
|
|
||||||
colorDesc.append("G ");
|
|
||||||
} else if (col.equalsIgnoreCase("Colorless")) {
|
|
||||||
colorDesc = new StringBuilder("C");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<String> imageNames = new ArrayList<>(1);
|
|
||||||
if (tokenImage.equals("")) {
|
|
||||||
imageNames.add(PaperToken.makeTokenFileName(TextUtil.fastReplace(colorDesc.toString(), " ", ""), tokenPower, tokenToughness, tokenName));
|
|
||||||
} else {
|
|
||||||
imageNames.add(0, tokenImage);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final char c : colorDesc.toString().toCharArray()) {
|
|
||||||
cost.append(c + ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
cost = new StringBuilder(colorDesc.toString().replace('C', '1').trim());
|
|
||||||
|
|
||||||
final int finalPower = AbilityUtils.calculateAmount(host, tokenPower, sa);
|
|
||||||
final int finalToughness = AbilityUtils.calculateAmount(host, tokenToughness, sa);
|
|
||||||
|
|
||||||
final String[] substitutedTypes = Arrays.copyOf(tokenTypes, tokenTypes.length);
|
|
||||||
for (int i = 0; i < substitutedTypes.length; i++) {
|
|
||||||
if (substitutedTypes[i].equals("ChosenType")) {
|
|
||||||
substitutedTypes[i] = host.getChosenType();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final String substitutedName = tokenName.equals("ChosenType") ? host.getChosenType() : tokenName;
|
|
||||||
final String imageName = imageNames.get(MyRandom.getRandom().nextInt(imageNames.size()));
|
|
||||||
final TokenInfo tokenInfo = new TokenInfo(substitutedName, imageName,
|
|
||||||
cost.toString(), substitutedTypes, tokenKeywords, finalPower, finalToughness);
|
|
||||||
Card token = tokenInfo.makeOneToken(ai);
|
|
||||||
|
|
||||||
if (token == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grant rule changes
|
|
||||||
if (tokenHiddenKeywords != null) {
|
|
||||||
for (final String s : tokenHiddenKeywords) {
|
|
||||||
token.addHiddenExtrinsicKeyword(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grant abilities
|
|
||||||
if (tokenAbilities != null) {
|
|
||||||
for (final String s : tokenAbilities) {
|
|
||||||
final String actualAbility = host.getSVar(s);
|
|
||||||
final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, token);
|
|
||||||
token.addSpellAbility(grantedAbility);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grant triggers
|
|
||||||
if (tokenTriggers != null) {
|
|
||||||
for (final String s : tokenTriggers) {
|
|
||||||
final String actualTrigger = host.getSVar(s);
|
|
||||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, token, true);
|
|
||||||
final String ability = host.getSVar(parsedTrigger.getParam("Execute"));
|
|
||||||
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(ability, token));
|
|
||||||
token.addTrigger(parsedTrigger);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grant SVars
|
|
||||||
if (tokenSVars != null) {
|
|
||||||
for (final String s : tokenSVars) {
|
|
||||||
String actualSVar = host.getSVar(s);
|
|
||||||
String name = s;
|
|
||||||
if (actualSVar.startsWith("SVar")) {
|
|
||||||
actualSVar = actualSVar.split("SVar:")[1];
|
|
||||||
name = actualSVar.split(":")[0];
|
|
||||||
actualSVar = actualSVar.split(":")[1];
|
|
||||||
}
|
|
||||||
token.setSVar(name, actualSVar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grant static abilities
|
|
||||||
if (tokenStaticAbilities != null) {
|
|
||||||
for (final String s : tokenStaticAbilities) {
|
|
||||||
token.addStaticAbility(host.getSVar(s));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply static abilities and prune dead tokens
|
|
||||||
final Game game = ai.getGame();
|
|
||||||
ComputerUtilCard.applyStaticContPT(game, token, null);
|
|
||||||
if (!notNull && token.isCreature() && token.getNetToughness() < 1) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import forge.game.spellability.TargetRestrictions;
|
|||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class UntapAi extends SpellAbilityAi {
|
public class UntapAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
@@ -153,12 +154,11 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// Try to avoid potential infinite recursion,
|
// Try to avoid potential infinite recursion,
|
||||||
// e.g. Kiora's Follower untapping another Kiora's Follower and repeating infinitely
|
// e.g. Kiora's Follower untapping another Kiora's Follower and repeating infinitely
|
||||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasOnlySpecificCostType(CostTap.class)) {
|
if (sa.getPayCosts().hasOnlySpecificCostType(CostTap.class)) {
|
||||||
CardCollection toRemove = new CardCollection();
|
CardCollection toRemove = new CardCollection();
|
||||||
for (Card c : untapList) {
|
for (Card c : untapList) {
|
||||||
for (SpellAbility ab : c.getAllSpellAbilities()) {
|
for (SpellAbility ab : c.getAllSpellAbilities()) {
|
||||||
if (ab.getApi() == ApiType.Untap
|
if (ab.getApi() == ApiType.Untap
|
||||||
&& ab.getPayCosts() != null
|
|
||||||
&& ab.getPayCosts().hasOnlySpecificCostType(CostTap.class)
|
&& ab.getPayCosts().hasOnlySpecificCostType(CostTap.class)
|
||||||
&& ab.canTarget(source)) {
|
&& ab.canTarget(source)) {
|
||||||
toRemove.add(c);
|
toRemove.add(c);
|
||||||
@@ -312,7 +312,7 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> list, boolean isOptional, Player targetedPlayer) {
|
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> list, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
PlayerCollection pl = new PlayerCollection();
|
PlayerCollection pl = new PlayerCollection();
|
||||||
pl.add(ai);
|
pl.add(ai);
|
||||||
pl.addAll(ai.getAllies());
|
pl.addAll(ai.getAllies());
|
||||||
@@ -331,7 +331,7 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// See if there's anything to untap that is tapped and that doesn't untap during the next untap step by itself
|
// See if there's anything to untap that is tapped and that doesn't untap during the next untap step by itself
|
||||||
CardCollection noAutoUntap = CardLists.filter(untapList, Predicates.not(CardPredicates.canUntapPhaseController()));
|
CardCollection noAutoUntap = CardLists.filter(untapList, CardPredicates.hasKeyword("CARDNAME doesn't untap during your untap step."));
|
||||||
if (!noAutoUntap.isEmpty()) {
|
if (!noAutoUntap.isEmpty()) {
|
||||||
return ComputerUtilCard.getBestAI(noAutoUntap);
|
return ComputerUtilCard.getBestAI(noAutoUntap);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ public class VoteAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||||
|
if (params.containsKey("Voter")) {
|
||||||
|
Player p = (Player)params.get("Voter");
|
||||||
|
if (p.isOpponentOf(player)) {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (sa.getActivatingPlayer().isOpponentOf(player)) {
|
if (sa.getActivatingPlayer().isOpponentOf(player)) {
|
||||||
return min;
|
return min;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ public class GameCopier {
|
|||||||
newPlayer.addSpellCastThisTurn();
|
newPlayer.addSpellCastThisTurn();
|
||||||
for (int j = 0; j < origPlayer.getLandsPlayedThisTurn(); j++)
|
for (int j = 0; j < origPlayer.getLandsPlayedThisTurn(); j++)
|
||||||
newPlayer.addLandPlayedThisTurn();
|
newPlayer.addLandPlayedThisTurn();
|
||||||
newPlayer.setCounters(Maps.newEnumMap(origPlayer.getCounters()));
|
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
|
||||||
newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn());
|
newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn());
|
||||||
newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
|
newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
|
||||||
newPlayer.setPreventNextDamage(origPlayer.getPreventNextDamage());
|
newPlayer.setPreventNextDamage(origPlayer.getPreventNextDamage());
|
||||||
@@ -125,8 +125,6 @@ public class GameCopier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
origGame.validateSpabCache();
|
|
||||||
newGame.validateSpabCache();
|
|
||||||
|
|
||||||
// Undo effects first before calculating them below, to avoid them applying twice.
|
// Undo effects first before calculating them below, to avoid them applying twice.
|
||||||
for (StaticEffect effect : origGame.getStaticEffects().getEffects()) {
|
for (StaticEffect effect : origGame.getStaticEffects().getEffects()) {
|
||||||
@@ -330,7 +328,7 @@ public class GameCopier {
|
|||||||
|
|
||||||
Map<CounterType, Integer> counters = c.getCounters();
|
Map<CounterType, Integer> counters = c.getCounters();
|
||||||
if (!counters.isEmpty()) {
|
if (!counters.isEmpty()) {
|
||||||
newCard.setCounters(Maps.newEnumMap(counters));
|
newCard.setCounters(Maps.newHashMap(counters));
|
||||||
}
|
}
|
||||||
if (c.getChosenPlayer() != null) {
|
if (c.getChosenPlayer() != null) {
|
||||||
newCard.setChosenPlayer(playerMap.get(c.getChosenPlayer()));
|
newCard.setChosenPlayer(playerMap.get(c.getChosenPlayer()));
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package forge.ai.simulation;
|
|||||||
import forge.ai.CreatureEvaluator;
|
import forge.ai.CreatureEvaluator;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
@@ -29,6 +29,13 @@ public class GameStateEvaluator {
|
|||||||
if (phase.isAfter(PhaseType.COMBAT_DAMAGE) || evalGame.isGameOver()) {
|
if (phase.isAfter(PhaseType.COMBAT_DAMAGE) || evalGame.isGameOver()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
// If the current player has no creatures in play, there won't be any combat. This avoids
|
||||||
|
// an expensive game copy operation.
|
||||||
|
// Note: This is is safe to do because the simulation is based on the current game state,
|
||||||
|
// so there isn't a chance to play creatures in between.
|
||||||
|
if (evalGame.getPhaseHandler().getPlayerTurn().getCreaturesInPlay().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
GameCopier copier = new GameCopier(evalGame);
|
GameCopier copier = new GameCopier(evalGame);
|
||||||
Game gameCopy = copier.makeCopy();
|
Game gameCopy = copier.makeCopy();
|
||||||
gameCopy.getPhaseHandler().devAdvanceToPhase(PhaseType.COMBAT_DAMAGE);
|
gameCopy.getPhaseHandler().devAdvanceToPhase(PhaseType.COMBAT_DAMAGE);
|
||||||
@@ -147,7 +154,7 @@ public class GameStateEvaluator {
|
|||||||
// e.g. a 5 CMC permanent results in 200, whereas a 5/5 creature is ~225
|
// e.g. a 5 CMC permanent results in 200, whereas a 5/5 creature is ~225
|
||||||
int value = 50 + 30 * c.getCMC();
|
int value = 50 + 30 * c.getCMC();
|
||||||
if (c.isPlaneswalker()) {
|
if (c.isPlaneswalker()) {
|
||||||
value += 2 * c.getCounters(CounterType.LOYALTY);
|
value += 2 * c.getCounters(CounterEnumType.LOYALTY);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import forge.util.TextUtil;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class SpellAbilityPicker {
|
public class SpellAbilityPicker {
|
||||||
private Game game;
|
private Game game;
|
||||||
@@ -307,7 +308,7 @@ public class SpellAbilityPicker {
|
|||||||
if (conditions == null) {
|
if (conditions == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
List<PhaseType> phases = conditions.getPhases();
|
Set<PhaseType> phases = conditions.getPhases();
|
||||||
return phases.isEmpty() || phases.contains(PhaseType.MAIN1);
|
return phases.isEmpty() || phases.contains(PhaseType.MAIN1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>1.6.30-SNAPSHOT</version>
|
<version>1.6.35</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-core</artifactId>
|
<artifactId>forge-core</artifactId>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
<version>3.7</version>
|
<version>3.8.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import forge.util.ImageUtil;
|
|||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -111,7 +113,28 @@ public final class ImageKeys {
|
|||||||
file = findFile(dir, TextUtil.fastReplace(filename, "AE", "Ae"));
|
file = findFile(dir, TextUtil.fastReplace(filename, "AE", "Ae"));
|
||||||
if (file != null) { return file; }
|
if (file != null) { return file; }
|
||||||
}
|
}
|
||||||
|
//try fullborder...
|
||||||
|
if (filename.contains(".full")) {
|
||||||
|
String fullborderFile = TextUtil.fastReplace(filename, ".full", ".fullborder");
|
||||||
|
file = findFile(dir, fullborderFile);
|
||||||
|
if (file != null) { return file; }
|
||||||
|
// if there's a 1st art variant try without it for .fullborder images
|
||||||
|
file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder"));
|
||||||
|
if (file != null) { return file; }
|
||||||
|
// if there's an art variant try without it for .full images
|
||||||
|
file = findFile(dir, filename.replaceAll("[0-9].full]",".full"));
|
||||||
|
if (file != null) { return file; }
|
||||||
|
// if there's a 1st art variant try with it for .full images
|
||||||
|
file = findFile(dir, filename.replaceAll("[0-9]*.full", "1.full"));
|
||||||
|
if (file != null) { return file; }
|
||||||
|
}
|
||||||
|
//if an image, like phenomenon or planes is missing .full in their filenames but you have an existing images that have .full/.fullborder
|
||||||
|
if (!filename.contains(".full")) {
|
||||||
|
file = findFile(dir, TextUtil.addSuffix(filename,".full"));
|
||||||
|
if (file != null) { return file; }
|
||||||
|
file = findFile(dir, TextUtil.addSuffix(filename,".fullborder"));
|
||||||
|
if (file != null) { return file; }
|
||||||
|
}
|
||||||
// some S00 cards are really part of 6ED
|
// some S00 cards are really part of 6ED
|
||||||
String s2kAlias = getSetFolder("S00");
|
String s2kAlias = getSetFolder("S00");
|
||||||
if (filename.startsWith(s2kAlias)) {
|
if (filename.startsWith(s2kAlias)) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
public abstract class LobbyPlayer {
|
public abstract class LobbyPlayer {
|
||||||
protected String name;
|
protected String name;
|
||||||
private int avatarIndex = -1;
|
private int avatarIndex = -1;
|
||||||
|
private int sleeveIndex = -1;
|
||||||
private String avatarCardImageKey;
|
private String avatarCardImageKey;
|
||||||
|
|
||||||
public LobbyPlayer(String name) {
|
public LobbyPlayer(String name) {
|
||||||
@@ -59,9 +60,15 @@ public abstract class LobbyPlayer {
|
|||||||
public int getAvatarIndex() {
|
public int getAvatarIndex() {
|
||||||
return avatarIndex;
|
return avatarIndex;
|
||||||
}
|
}
|
||||||
|
public int getSleeveIndex() {
|
||||||
|
return sleeveIndex;
|
||||||
|
}
|
||||||
public void setAvatarIndex(int avatarIndex) {
|
public void setAvatarIndex(int avatarIndex) {
|
||||||
this.avatarIndex = avatarIndex;
|
this.avatarIndex = avatarIndex;
|
||||||
}
|
}
|
||||||
|
public void setSleeveIndex(int sleeveIndex) {
|
||||||
|
this.sleeveIndex = sleeveIndex;
|
||||||
|
}
|
||||||
|
|
||||||
public String getAvatarCardImageKey() {
|
public String getAvatarCardImageKey() {
|
||||||
return avatarCardImageKey;
|
return avatarCardImageKey;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public class StaticData {
|
|||||||
|
|
||||||
private Predicate<PaperCard> standardPredicate;
|
private Predicate<PaperCard> standardPredicate;
|
||||||
private Predicate<PaperCard> brawlPredicate;
|
private Predicate<PaperCard> brawlPredicate;
|
||||||
|
private Predicate<PaperCard> pioneerPredicate;
|
||||||
private Predicate<PaperCard> modernPredicate;
|
private Predicate<PaperCard> modernPredicate;
|
||||||
private Predicate<PaperCard> commanderPredicate;
|
private Predicate<PaperCard> commanderPredicate;
|
||||||
private Predicate<PaperCard> oathbreakerPredicate;
|
private Predicate<PaperCard> oathbreakerPredicate;
|
||||||
@@ -53,11 +54,11 @@ public class StaticData {
|
|||||||
|
|
||||||
private static StaticData lastInstance = null;
|
private static StaticData lastInstance = null;
|
||||||
|
|
||||||
public StaticData(CardStorageReader cardReader, String editionFolder, String blockDataFolder) {
|
public StaticData(CardStorageReader cardReader, String editionFolder, String blockDataFolder, boolean enableUnknownCards) {
|
||||||
this(cardReader, null, editionFolder, blockDataFolder);
|
this(cardReader, null, editionFolder, blockDataFolder, enableUnknownCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, String editionFolder, String blockDataFolder) {
|
public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, String editionFolder, String blockDataFolder, boolean enableUnknownCards) {
|
||||||
this.cardReader = cardReader;
|
this.cardReader = cardReader;
|
||||||
this.tokenReader = tokenReader;
|
this.tokenReader = tokenReader;
|
||||||
this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder)));
|
this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder)));
|
||||||
@@ -83,8 +84,8 @@ public class StaticData {
|
|||||||
variantCards = new CardDb(variantsCards, editions);
|
variantCards = new CardDb(variantsCards, editions);
|
||||||
|
|
||||||
//must initialize after establish field values for the sake of card image logic
|
//must initialize after establish field values for the sake of card image logic
|
||||||
commonCards.initialize(false, false);
|
commonCards.initialize(false, false, enableUnknownCards);
|
||||||
variantCards.initialize(false, false);
|
variantCards.initialize(false, false, enableUnknownCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -197,13 +198,13 @@ public class StaticData {
|
|||||||
|
|
||||||
public TokenDb getAllTokens() { return allTokens; }
|
public TokenDb getAllTokens() { return allTokens; }
|
||||||
|
|
||||||
public Predicate<PaperCard> getStandardPredicate() {
|
|
||||||
return standardPredicate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStandardPredicate(Predicate<PaperCard> standardPredicate) { this.standardPredicate = standardPredicate; }
|
public void setStandardPredicate(Predicate<PaperCard> standardPredicate) { this.standardPredicate = standardPredicate; }
|
||||||
|
|
||||||
public void setModernPredicate(Predicate<PaperCard> modernPredicate) { this.modernPredicate = standardPredicate; }
|
public void setPioneerPredicate(Predicate<PaperCard> pioneerPredicate) { this.pioneerPredicate = pioneerPredicate; }
|
||||||
|
|
||||||
|
public void setModernPredicate(Predicate<PaperCard> modernPredicate) { this.modernPredicate = modernPredicate; }
|
||||||
|
|
||||||
public void setCommanderPredicate(Predicate<PaperCard> commanderPredicate) { this.commanderPredicate = commanderPredicate; }
|
public void setCommanderPredicate(Predicate<PaperCard> commanderPredicate) { this.commanderPredicate = commanderPredicate; }
|
||||||
|
|
||||||
@@ -211,9 +212,11 @@ public class StaticData {
|
|||||||
|
|
||||||
public void setBrawlPredicate(Predicate<PaperCard> brawlPredicate) { this.brawlPredicate = brawlPredicate; }
|
public void setBrawlPredicate(Predicate<PaperCard> brawlPredicate) { this.brawlPredicate = brawlPredicate; }
|
||||||
|
|
||||||
public Predicate<PaperCard> getModernPredicate() {
|
public Predicate<PaperCard> getStandardPredicate() { return standardPredicate; }
|
||||||
return modernPredicate;
|
|
||||||
}
|
public Predicate<PaperCard> getPioneerPredicate() { return pioneerPredicate; }
|
||||||
|
|
||||||
|
public Predicate<PaperCard> getModernPredicate() { return modernPredicate; }
|
||||||
|
|
||||||
public Predicate<PaperCard> getCommanderPredicate() { return commanderPredicate; }
|
public Predicate<PaperCard> getCommanderPredicate() { return commanderPredicate; }
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
reIndex();
|
reIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize(boolean logMissingPerEdition, boolean logMissingSummary) {
|
public void initialize(boolean logMissingPerEdition, boolean logMissingSummary, boolean enableUnknownCards) {
|
||||||
Set<String> allMissingCards = new LinkedHashSet<>();
|
Set<String> allMissingCards = new LinkedHashSet<>();
|
||||||
List<String> missingCards = new ArrayList<>();
|
List<String> missingCards = new ArrayList<>();
|
||||||
CardEdition upcomingSet = null;
|
CardEdition upcomingSet = null;
|
||||||
@@ -218,7 +218,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
if (!contains(cr.getName())) {
|
if (!contains(cr.getName())) {
|
||||||
if (upcomingSet != null) {
|
if (upcomingSet != null) {
|
||||||
addCard(new PaperCard(cr, upcomingSet.getCode(), CardRarity.Unknown, 1));
|
addCard(new PaperCard(cr, upcomingSet.getCode(), CardRarity.Unknown, 1));
|
||||||
} else {
|
} else if(enableUnknownCards) {
|
||||||
System.err.println("The card " + cr.getName() + " was not assigned to any set. Adding it to UNKNOWN set... to fix see res/editions/ folder. ");
|
System.err.println("The card " + cr.getName() + " was not assigned to any set. Adding it to UNKNOWN set... to fix see res/editions/ folder. ");
|
||||||
addCard(new PaperCard(cr, CardEdition.UNKNOWN.getCode(), CardRarity.Special, 1));
|
addCard(new PaperCard(cr, CardEdition.UNKNOWN.getCode(), CardRarity.Special, 1));
|
||||||
}
|
}
|
||||||
@@ -312,17 +312,21 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
return tryGetCard(request);
|
return tryGetCard(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCardCollectorNumber(String cardName, String reqEdition) {
|
public String getCardCollectorNumber(String cardName, String reqEdition, int artIndex) {
|
||||||
cardName = getName(cardName);
|
cardName = getName(cardName);
|
||||||
CardEdition edition = editions.get(reqEdition);
|
CardEdition edition = editions.get(reqEdition);
|
||||||
if (edition == null)
|
if (edition == null)
|
||||||
return -1;
|
return null;
|
||||||
|
int numMatches = 0;
|
||||||
for (CardInSet card : edition.getCards()) {
|
for (CardInSet card : edition.getCards()) {
|
||||||
if (card.name.equalsIgnoreCase(cardName)) {
|
if (card.name.equalsIgnoreCase(cardName)) {
|
||||||
|
numMatches += 1;
|
||||||
|
if (numMatches == artIndex) {
|
||||||
return card.collectorNumber;
|
return card.collectorNumber;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PaperCard tryGetCard(CardRequest request) {
|
private PaperCard tryGetCard(CardRequest request) {
|
||||||
@@ -553,6 +557,23 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
return Lists.newArrayList(Iterables.filter(this.roAllCards, predicate));
|
return Lists.newArrayList(Iterables.filter(this.roAllCards, predicate));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do I want a foiled version of these cards?
|
||||||
|
@Override
|
||||||
|
public List<PaperCard> getAllCardsFromEdition(CardEdition edition) {
|
||||||
|
List<PaperCard> cards = Lists.newArrayList();
|
||||||
|
|
||||||
|
for(CardInSet cis : edition.getCards()) {
|
||||||
|
PaperCard card = this.getCard(cis.name, edition.getCode());
|
||||||
|
if (card == null) {
|
||||||
|
// Just in case the card is listed in the edition file but Forge doesn't support it
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cards.add(card);
|
||||||
|
}
|
||||||
|
return cards;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(String name) {
|
public boolean contains(String name) {
|
||||||
return allCardsByName.containsKey(getName(name));
|
return allCardsByName.containsKey(getName(name));
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ import java.text.ParseException;
|
|||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,10 +77,10 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
|
|
||||||
public static class CardInSet {
|
public static class CardInSet {
|
||||||
public final CardRarity rarity;
|
public final CardRarity rarity;
|
||||||
public final int collectorNumber;
|
public final String collectorNumber;
|
||||||
public final String name;
|
public final String name;
|
||||||
|
|
||||||
public CardInSet(final String name, final int collectorNumber, final CardRarity rarity) {
|
public CardInSet(final String name, final String collectorNumber, final CardRarity rarity) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.collectorNumber = collectorNumber;
|
this.collectorNumber = collectorNumber;
|
||||||
this.rarity = rarity;
|
this.rarity = rarity;
|
||||||
@@ -86,7 +88,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
if (collectorNumber != -1) {
|
if (collectorNumber != null) {
|
||||||
sb.append(collectorNumber);
|
sb.append(collectorNumber);
|
||||||
sb.append(' ');
|
sb.append(' ');
|
||||||
}
|
}
|
||||||
@@ -110,6 +112,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
private Type type;
|
private Type type;
|
||||||
private String name;
|
private String name;
|
||||||
private String alias = null;
|
private String alias = null;
|
||||||
|
private String prerelease = null;
|
||||||
private boolean whiteBorder = false;
|
private boolean whiteBorder = false;
|
||||||
private FoilType foilType = FoilType.NOT_SUPPORTED;
|
private FoilType foilType = FoilType.NOT_SUPPORTED;
|
||||||
private double foilChanceInBooster = 0;
|
private double foilChanceInBooster = 0;
|
||||||
@@ -178,6 +181,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
public Type getType() { return type; }
|
public Type getType() { return type; }
|
||||||
public String getName() { return name; }
|
public String getName() { return name; }
|
||||||
public String getAlias() { return alias; }
|
public String getAlias() { return alias; }
|
||||||
|
public String getPrerelease() { return prerelease; }
|
||||||
public FoilType getFoilType() { return foilType; }
|
public FoilType getFoilType() { return foilType; }
|
||||||
public double getFoilChanceInBooster() { return foilChanceInBooster; }
|
public double getFoilChanceInBooster() { return foilChanceInBooster; }
|
||||||
public boolean getFoilAlwaysInCommonSlot() { return foilAlwaysInCommonSlot; }
|
public boolean getFoilAlwaysInCommonSlot() { return foilAlwaysInCommonSlot; }
|
||||||
@@ -188,6 +192,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
public boolean getSmallSetOverride() { return smallSetOverride; }
|
public boolean getSmallSetOverride() { return smallSetOverride; }
|
||||||
public String getBoosterMustContain() { return boosterMustContain; }
|
public String getBoosterMustContain() { return boosterMustContain; }
|
||||||
public CardInSet[] getCards() { return cards; }
|
public CardInSet[] getCards() { return cards; }
|
||||||
|
public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others
|
||||||
|
|
||||||
public Map<String, Integer> getTokens() { return tokenNormalized; }
|
public Map<String, Integer> getTokens() { return tokenNormalized; }
|
||||||
|
|
||||||
@@ -264,26 +269,35 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
Map<String, Integer> tokenNormalized = new HashMap<>();
|
Map<String, Integer> tokenNormalized = new HashMap<>();
|
||||||
List<CardEdition.CardInSet> processedCards = new ArrayList<>();
|
List<CardEdition.CardInSet> processedCards = new ArrayList<>();
|
||||||
if (contents.containsKey("cards")) {
|
if (contents.containsKey("cards")) {
|
||||||
|
final Pattern pattern = Pattern.compile(
|
||||||
|
/*
|
||||||
|
The following pattern will match the WAR Japanese art entries,
|
||||||
|
it should also match the Un-set and older alternate art cards
|
||||||
|
like Merseine from FEM (should the editions files ever be updated)
|
||||||
|
*/
|
||||||
|
//"(^(?<cnum>[0-9]+.?) )?((?<rarity>[SCURML]) )?(?<name>.*)$"
|
||||||
|
/* Ideally we'd use the named group above, but Android 6 and
|
||||||
|
earlier don't appear to support named groups.
|
||||||
|
So, untill support for those devices is officially dropped,
|
||||||
|
we'll have to suffice with numbered groups.
|
||||||
|
We are looking for:
|
||||||
|
* cnum - grouping #2
|
||||||
|
* rarity - grouping #4
|
||||||
|
* name - grouping #5
|
||||||
|
*/
|
||||||
|
"(^([0-9]+.?) )?(([SCURML]) )?(.*)$"
|
||||||
|
);
|
||||||
for(String line : contents.get("cards")) {
|
for(String line : contents.get("cards")) {
|
||||||
if (StringUtils.isBlank(line))
|
Matcher matcher = pattern.matcher(line);
|
||||||
continue;
|
if (matcher.matches()) {
|
||||||
|
String collectorNumber = matcher.group(2);
|
||||||
// Optional collector number at the start.
|
CardRarity r = CardRarity.smartValueOf(matcher.group(4));
|
||||||
String[] split = line.split(" ", 2);
|
String cardName = matcher.group(5);
|
||||||
int collectorNumber = -1;
|
|
||||||
if (split.length >= 2 && StringUtils.isNumeric(split[0])) {
|
|
||||||
collectorNumber = Integer.parseInt(split[0]);
|
|
||||||
line = split[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// You may omit rarity for early development
|
|
||||||
CardRarity r = CardRarity.smartValueOf(line.substring(0, 1));
|
|
||||||
boolean hadRarity = r != CardRarity.Unknown && line.charAt(1) == ' ';
|
|
||||||
String cardName = hadRarity ? line.substring(2) : line;
|
|
||||||
CardInSet cis = new CardInSet(cardName, collectorNumber, r);
|
CardInSet cis = new CardInSet(cardName, collectorNumber, r);
|
||||||
processedCards.add(cis);
|
processedCards.add(cis);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (contents.containsKey("tokens")) {
|
if (contents.containsKey("tokens")) {
|
||||||
for(String line : contents.get("tokens")) {
|
for(String line : contents.get("tokens")) {
|
||||||
@@ -303,7 +317,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
tokenNormalized
|
tokenNormalized
|
||||||
);
|
);
|
||||||
|
|
||||||
FileSection section = FileSection.parse(contents.get("metadata"), "=");
|
FileSection section = FileSection.parse(contents.get("metadata"), FileSection.EQUALS_KV_SEPARATOR);
|
||||||
res.name = section.get("name");
|
res.name = section.get("name");
|
||||||
res.date = parseDate(section.get("date"));
|
res.date = parseDate(section.get("date"));
|
||||||
res.code = section.get("code");
|
res.code = section.get("code");
|
||||||
@@ -333,6 +347,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.type = enumType;
|
res.type = enumType;
|
||||||
|
res.prerelease = section.get("Prerelease", null);
|
||||||
|
|
||||||
switch(section.get("foil", "newstyle").toLowerCase()) {
|
switch(section.get("foil", "newstyle").toLowerCase()) {
|
||||||
case "notsupported":
|
case "notsupported":
|
||||||
@@ -413,6 +428,16 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Iterable<CardEdition> getPrereleaseEditions() {
|
||||||
|
List<CardEdition> res = Lists.newArrayList(this);
|
||||||
|
return Iterables.filter(res, new Predicate<CardEdition>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(final CardEdition edition) {
|
||||||
|
return edition.getPrerelease() != null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public CardEdition getEditionByCodeOrThrow(final String code) {
|
public CardEdition getEditionByCodeOrThrow(final String code) {
|
||||||
final CardEdition set = this.get(code);
|
final CardEdition set = this.get(code);
|
||||||
if (null == set) {
|
if (null == set) {
|
||||||
|
|||||||
@@ -222,7 +222,12 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
|
|
||||||
public boolean canBeBrawlCommander() {
|
public boolean canBeBrawlCommander() {
|
||||||
CardType type = mainPart.getType();
|
CardType type = mainPart.getType();
|
||||||
return (type.isLegendary() && type.isCreature()) || type.isPlaneswalker();
|
return type.isLegendary() && (type.isCreature() || type.isPlaneswalker());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canBeTinyLeadersCommander() {
|
||||||
|
CardType type = mainPart.getType();
|
||||||
|
return type.isLegendary() && (type.isCreature() || type.isPlaneswalker());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMeldWith() {
|
public String getMeldWith() {
|
||||||
@@ -526,12 +531,10 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
public final ManaCostShard next() {
|
public final ManaCostShard next() {
|
||||||
final String unparsed = st.nextToken();
|
final String unparsed = st.nextToken();
|
||||||
// System.out.println(unparsed);
|
// System.out.println(unparsed);
|
||||||
try {
|
if (StringUtils.isNumeric(unparsed)) {
|
||||||
int iVal = Integer.parseInt(unparsed);
|
this.genericCost += Integer.parseInt(unparsed);
|
||||||
this.genericCost += iVal;
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (NumberFormatException nex) { }
|
|
||||||
|
|
||||||
return ManaCostShard.parseNonGeneric(unparsed);
|
return ManaCostShard.parseNonGeneric(unparsed);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -594,8 +594,10 @@ public final class CardRulesPredicates {
|
|||||||
public static final Predicate<CardRules> IS_VANGUARD = CardRulesPredicates.coreType(true, CardType.CoreType.Vanguard);
|
public static final Predicate<CardRules> IS_VANGUARD = CardRulesPredicates.coreType(true, CardType.CoreType.Vanguard);
|
||||||
public static final Predicate<CardRules> IS_CONSPIRACY = CardRulesPredicates.coreType(true, CardType.CoreType.Conspiracy);
|
public static final Predicate<CardRules> IS_CONSPIRACY = CardRulesPredicates.coreType(true, CardType.CoreType.Conspiracy);
|
||||||
public static final Predicate<CardRules> IS_NON_LAND = CardRulesPredicates.coreType(false, CardType.CoreType.Land);
|
public static final Predicate<CardRules> IS_NON_LAND = CardRulesPredicates.coreType(false, CardType.CoreType.Land);
|
||||||
public static final Predicate<CardRules> CAN_BE_BRAWL_COMMANDER = Predicates.or(Presets.IS_PLANESWALKER,
|
public static final Predicate<CardRules> CAN_BE_BRAWL_COMMANDER = Predicates.and(Presets.IS_LEGENDARY,
|
||||||
Predicates.and(Presets.IS_CREATURE, Presets.IS_LEGENDARY));
|
Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER));
|
||||||
|
public static final Predicate<CardRules> CAN_BE_TINY_LEADERS_COMMANDER = Predicates.and(Presets.IS_LEGENDARY,
|
||||||
|
Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER));
|
||||||
|
|
||||||
/** The Constant IS_NON_CREATURE_SPELL. **/
|
/** The Constant IS_NON_CREATURE_SPELL. **/
|
||||||
public static final Predicate<CardRules> IS_NON_CREATURE_SPELL = com.google.common.base.Predicates
|
public static final Predicate<CardRules> IS_NON_CREATURE_SPELL = com.google.common.base.Predicates
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.EnumUtils;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
@@ -35,10 +36,8 @@ import com.google.common.collect.HashBiMap;
|
|||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
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.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
import forge.util.EnumUtil;
|
|
||||||
import forge.util.Settable;
|
import forge.util.Settable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,7 +46,6 @@ import forge.util.Settable;
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author Forge
|
* @author Forge
|
||||||
* @version $Id: java 9708 2011-08-09 19:34:12Z jendave $
|
|
||||||
*/
|
*/
|
||||||
public final class CardType implements Comparable<CardType>, CardTypeView {
|
public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||||
private static final long serialVersionUID = 4629853583167022151L;
|
private static final long serialVersionUID = 4629853583167022151L;
|
||||||
@@ -71,7 +69,16 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
Vanguard(false);
|
Vanguard(false);
|
||||||
|
|
||||||
public final boolean isPermanent;
|
public final boolean isPermanent;
|
||||||
private static final ImmutableList<String> allCoreTypeNames = EnumUtil.getNames(CoreType.class);
|
private static Map<String, CoreType> stringToCoreType = EnumUtils.getEnumMap(CoreType.class);
|
||||||
|
private static final Set<String> allCoreTypeNames = stringToCoreType.keySet();
|
||||||
|
|
||||||
|
public static CoreType getEnum(String name) {
|
||||||
|
return stringToCoreType.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidEnum(String name) {
|
||||||
|
return stringToCoreType.containsKey(name);
|
||||||
|
}
|
||||||
|
|
||||||
CoreType(final boolean permanent) {
|
CoreType(final boolean permanent) {
|
||||||
isPermanent = permanent;
|
isPermanent = permanent;
|
||||||
@@ -86,19 +93,17 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
Ongoing,
|
Ongoing,
|
||||||
World;
|
World;
|
||||||
|
|
||||||
private static final ImmutableList<String> allSuperTypeNames = EnumUtil.getNames(Supertype.class);
|
private static Map<String, Supertype> stringToSupertype = EnumUtils.getEnumMap(Supertype.class);
|
||||||
|
private static final Set<String> allSuperTypeNames = stringToSupertype.keySet();
|
||||||
|
|
||||||
|
public static Supertype getEnum(String name) {
|
||||||
|
return stringToSupertype.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will be useful for faster parses
|
public static boolean isValidEnum(String name) {
|
||||||
private static Map<String, CoreType> stringToCoreType = Maps.newHashMap();
|
return stringToSupertype.containsKey(name);
|
||||||
private static Map<String, Supertype> stringToSupertype = Maps.newHashMap();
|
|
||||||
static {
|
|
||||||
for (final Supertype st : Supertype.values()) {
|
|
||||||
stringToSupertype.put(st.name(), st);
|
|
||||||
}
|
|
||||||
for (final CoreType ct : CoreType.values()) {
|
|
||||||
stringToCoreType.put(ct.name(), ct);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Set<CoreType> coreTypes = EnumSet.noneOf(CoreType.class);
|
private final Set<CoreType> coreTypes = EnumSet.noneOf(CoreType.class);
|
||||||
@@ -120,12 +125,12 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
|
|
||||||
public boolean add(final String t) {
|
public boolean add(final String t) {
|
||||||
boolean changed;
|
boolean changed;
|
||||||
final CoreType ct = stringToCoreType.get(t);
|
final CoreType ct = CoreType.getEnum(t);
|
||||||
if (ct != null) {
|
if (ct != null) {
|
||||||
changed = coreTypes.add(ct);
|
changed = coreTypes.add(ct);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
final Supertype st = stringToSupertype.get(t);
|
final Supertype st = Supertype.getEnum(t);
|
||||||
if (st != null) {
|
if (st != null) {
|
||||||
changed = supertypes.add(st);
|
changed = supertypes.add(st);
|
||||||
}
|
}
|
||||||
@@ -190,13 +195,21 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
|
|
||||||
public boolean remove(final String str) {
|
public boolean remove(final String str) {
|
||||||
boolean changed = false;
|
boolean changed = false;
|
||||||
if (CardType.isASupertype(str) && supertypes.remove(stringToSupertype.get(str))) {
|
|
||||||
|
// try to remove sub type first if able
|
||||||
|
if (subtypes.remove(str)) {
|
||||||
changed = true;
|
changed = true;
|
||||||
} else if (CardType.isACardType(str) && coreTypes.remove(stringToCoreType.get(str))) {
|
} else {
|
||||||
changed = true;
|
Supertype st = Supertype.getEnum(str);
|
||||||
} else if (subtypes.remove(str)) {
|
if (st != null && supertypes.remove(st)) {
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
CoreType ct = CoreType.getEnum(str);
|
||||||
|
if (ct != null && coreTypes.remove(ct)) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
calculatedType = null;
|
calculatedType = null;
|
||||||
}
|
}
|
||||||
@@ -267,15 +280,13 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
if (hasSubtype(t)) {
|
if (hasSubtype(t)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
final char firstChar = t.charAt(0);
|
|
||||||
if (Character.isLowerCase(firstChar)) {
|
t = StringUtils.capitalize(t);
|
||||||
t = Character.toUpperCase(firstChar) + t.substring(1); //ensure string is proper case for enum types
|
final CoreType type = CoreType.getEnum(t);
|
||||||
}
|
|
||||||
final CoreType type = stringToCoreType.get(t);
|
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
return hasType(type);
|
return hasType(type);
|
||||||
}
|
}
|
||||||
final Supertype supertype = stringToSupertype.get(t);
|
final Supertype supertype = Supertype.getEnum(t);
|
||||||
if (supertype != null) {
|
if (supertype != null) {
|
||||||
return hasSupertype(supertype);
|
return hasSupertype(supertype);
|
||||||
}
|
}
|
||||||
@@ -307,18 +318,18 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
return subtypes.contains(creatureType) || subtypes.contains("AllCreatureTypes");
|
return subtypes.contains(creatureType) || subtypes.contains("AllCreatureTypes");
|
||||||
}
|
}
|
||||||
private static String toMixedCase(final String s) {
|
private static String toMixedCase(final String s) {
|
||||||
if (s.equals("")) {
|
if (s.isEmpty()) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
// to handle hyphenated Types
|
// to handle hyphenated Types
|
||||||
|
// TODO checkout WordUtils for this
|
||||||
final String[] types = s.split("-");
|
final String[] types = s.split("-");
|
||||||
for (int i = 0; i < types.length; i++) {
|
for (int i = 0; i < types.length; i++) {
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
sb.append("-");
|
sb.append("-");
|
||||||
}
|
}
|
||||||
sb.append(types[i].substring(0, 1).toUpperCase());
|
sb.append(StringUtils.capitalize(types[i]));
|
||||||
sb.append(types[i].substring(1).toLowerCase());
|
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
@@ -606,13 +617,13 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
|
|
||||||
public static class Constant {
|
public static class Constant {
|
||||||
public static final Settable LOADED = new Settable();
|
public static final Settable LOADED = new Settable();
|
||||||
public static final List<String> BASIC_TYPES = Lists.newArrayList();
|
public static final Set<String> BASIC_TYPES = Sets.newHashSet();
|
||||||
public static final List<String> LAND_TYPES = Lists.newArrayList();
|
public static final Set<String> LAND_TYPES = Sets.newHashSet();
|
||||||
public static final List<String> CREATURE_TYPES = Lists.newArrayList();
|
public static final Set<String> CREATURE_TYPES = Sets.newHashSet();
|
||||||
public static final List<String> SPELL_TYPES = Lists.newArrayList();
|
public static final Set<String> SPELL_TYPES = Sets.newHashSet();
|
||||||
public static final List<String> ENCHANTMENT_TYPES = Lists.newArrayList();
|
public static final Set<String> ENCHANTMENT_TYPES = Sets.newHashSet();
|
||||||
public static final List<String> ARTIFACT_TYPES = Lists.newArrayList();
|
public static final Set<String> ARTIFACT_TYPES = Sets.newHashSet();
|
||||||
public static final List<String> WALKER_TYPES = Lists.newArrayList();
|
public static final Set<String> WALKER_TYPES = Sets.newHashSet();
|
||||||
|
|
||||||
// singular -> plural
|
// singular -> plural
|
||||||
public static final BiMap<String,String> pluralTypes = HashBiMap.create();
|
public static final BiMap<String,String> pluralTypes = HashBiMap.create();
|
||||||
@@ -666,10 +677,10 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
|
|
||||||
///////// Utility methods
|
///////// Utility methods
|
||||||
public static boolean isACardType(final String cardType) {
|
public static boolean isACardType(final String cardType) {
|
||||||
return getAllCardTypes().contains(cardType);
|
return CoreType.isValidEnum(cardType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ImmutableList<String> getAllCardTypes() {
|
public static Set<String> getAllCardTypes() {
|
||||||
return CoreType.allCoreTypeNames;
|
return CoreType.allCoreTypeNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -699,12 +710,12 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
return sortedSubTypes;
|
return sortedSubTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> getBasicTypes() {
|
public static Collection<String> getBasicTypes() {
|
||||||
return Collections.unmodifiableList(Constant.BASIC_TYPES);
|
return Collections.unmodifiableCollection(Constant.BASIC_TYPES);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> getAllCreatureTypes() {
|
public static Collection<String> getAllCreatureTypes() {
|
||||||
return Collections.unmodifiableList(Constant.CREATURE_TYPES);
|
return Collections.unmodifiableCollection(Constant.CREATURE_TYPES);
|
||||||
}
|
}
|
||||||
public static List<String> getAllLandTypes() {
|
public static List<String> getAllLandTypes() {
|
||||||
return ImmutableList.<String>builder()
|
return ImmutableList.<String>builder()
|
||||||
@@ -714,7 +725,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isASupertype(final String cardType) {
|
public static boolean isASupertype(final String cardType) {
|
||||||
return (Supertype.allSuperTypeNames.contains(cardType));
|
return Supertype.isValidEnum(cardType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isASubType(final String cardType) {
|
public static boolean isASubType(final String cardType) {
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ public interface ICardDatabase extends Iterable<PaperCard> {
|
|||||||
List<PaperCard> getAllCards(String cardName);
|
List<PaperCard> getAllCards(String cardName);
|
||||||
List<PaperCard> getAllCards(Predicate<PaperCard> predicate);
|
List<PaperCard> getAllCards(Predicate<PaperCard> predicate);
|
||||||
|
|
||||||
|
List<PaperCard> getAllCardsFromEdition(CardEdition edition);
|
||||||
|
|
||||||
Predicate<? super PaperCard> wasPrintedInSets(List<String> allowedSetCodes);
|
Predicate<? super PaperCard> wasPrintedInSets(List<String> allowedSetCodes);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -289,13 +289,20 @@ public enum ManaCostShard {
|
|||||||
return BinaryUtil.bitCount(this.shard & COLORS_SUPERPOSITION) == 2;
|
return BinaryUtil.bitCount(this.shard & COLORS_SUPERPOSITION) == 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isGeneric() {
|
||||||
|
return isOfKind(ManaAtom.GENERIC)|| isOfKind(ManaAtom.IS_X) || this.isSnow() || this.isOr2Generic();
|
||||||
|
}
|
||||||
public boolean isOr2Generic() {
|
public boolean isOr2Generic() {
|
||||||
return isOfKind(ManaAtom.OR_2_GENERIC);
|
return isOfKind(ManaAtom.OR_2_GENERIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isColor(byte colorCode) {
|
||||||
|
return (colorCode & this.shard) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean canBePaidWithManaOfColor(byte colorCode) {
|
public boolean canBePaidWithManaOfColor(byte colorCode) {
|
||||||
return this.isOr2Generic() || ((COLORS_SUPERPOSITION | ManaAtom.COLORLESS) & this.shard) == 0 ||
|
return this.isOr2Generic() || ((COLORS_SUPERPOSITION | ManaAtom.COLORLESS) & this.shard) == 0 ||
|
||||||
(colorCode & this.shard) > 0;
|
this.isColor(colorCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOfKind(int atom) {
|
public boolean isOfKind(int atom) {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
import forge.card.CardRules;
|
import forge.card.CardRules;
|
||||||
import forge.card.CardRulesPredicates;
|
import forge.card.CardRulesPredicates;
|
||||||
@@ -37,8 +36,11 @@ import forge.util.TextUtil;
|
|||||||
import org.apache.commons.lang3.Range;
|
import org.apache.commons.lang3.Range;
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GameType is an enum to determine the type of current game. :)
|
* GameType is an enum to determine the type of current game. :)
|
||||||
@@ -71,7 +73,7 @@ public enum DeckFormat {
|
|||||||
private final Set<String> bannedCards = ImmutableSet.of(
|
private final Set<String> bannedCards = ImmutableSet.of(
|
||||||
"Ancestral Recall", "Balance", "Black Lotus", "Black Vise", "Channel", "Chaos Orb", "Contract From Below", "Counterbalance", "Darkpact", "Demonic Attorney", "Demonic Tutor", "Earthcraft", "Edric, Spymaster of Trest", "Falling Star",
|
"Ancestral Recall", "Balance", "Black Lotus", "Black Vise", "Channel", "Chaos Orb", "Contract From Below", "Counterbalance", "Darkpact", "Demonic Attorney", "Demonic Tutor", "Earthcraft", "Edric, Spymaster of Trest", "Falling Star",
|
||||||
"Fastbond", "Flash", "Goblin Recruiter", "Grindstone", "Hermit Druid", "Imperial Seal", "Jeweled Bird", "Karakas", "Library of Alexandria", "Mana Crypt", "Mana Drain", "Mana Vault", "Metalworker", "Mind Twist", "Mishra's Workshop",
|
"Fastbond", "Flash", "Goblin Recruiter", "Grindstone", "Hermit Druid", "Imperial Seal", "Jeweled Bird", "Karakas", "Library of Alexandria", "Mana Crypt", "Mana Drain", "Mana Vault", "Metalworker", "Mind Twist", "Mishra's Workshop",
|
||||||
"Mox Emerald", "Mox Jet", "Mox Pearl", "Mox Ruby", "Mox Sapphire", "Necropotence", "Shahrazad", "Skullclamp", "Sol Ring", "Strip Mine", "Survival of the Fittest", "Sword of Body and Mind", "Time Vault", "Time Walk", "Timetwister",
|
"Mox Emerald", "Mox Jet", "Mox Pearl", "Mox Ruby", "Mox Sapphire", "Najeela, the Blade Blossom", "Necropotence", "Shahrazad", "Skullclamp", "Sol Ring", "Strip Mine", "Survival of the Fittest", "Sword of Body and Mind", "Time Vault", "Time Walk", "Timetwister",
|
||||||
"Timmerian Fiends", "Tolarian Academy", "Umezawa's Jitte", "Vampiric Tutor", "Wheel of Fortune", "Yawgmoth's Will");
|
"Timmerian Fiends", "Tolarian Academy", "Umezawa's Jitte", "Vampiric Tutor", "Wheel of Fortune", "Yawgmoth's Will");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -324,23 +326,32 @@ public enum DeckFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final int maxCopies = getMaxCardCopies();
|
final int maxCopies = getMaxCardCopies();
|
||||||
if (maxCopies < Integer.MAX_VALUE) {
|
|
||||||
//Must contain no more than 4 of the same card
|
//Must contain no more than 4 of the same card
|
||||||
//shared among the main deck and sideboard, except
|
//shared among the main deck and sideboard, except
|
||||||
//basic lands, Shadowborn Apostle, Relentless Rats and Rat Colony
|
//basic lands, Shadowborn Apostle, Relentless Rats and Rat Colony
|
||||||
|
// Seven Dwarves can have 7 in the deck. More than 7 in deck + sb is ok in Limited
|
||||||
|
|
||||||
final CardPool allCards = deck.getAllCardsInASinglePool(hasCommander());
|
final CardPool allCards = deck.getAllCardsInASinglePool(hasCommander());
|
||||||
|
|
||||||
// should group all cards by name, so that different editions of same card are really counted as the same card
|
// should group all cards by name, so that different editions of same card are really counted as the same card
|
||||||
for (final Entry<String, Integer> cp : Aggregates.groupSumBy(allCards, PaperCard.FN_GET_NAME)) {
|
for (final Entry<String, Integer> cp : Aggregates.groupSumBy(allCards, PaperCard.FN_GET_NAME)) {
|
||||||
final IPaperCard simpleCard = StaticData.instance().getCommonCards().getCard(cp.getKey());
|
final IPaperCard simpleCard = StaticData.instance().getCommonCards().getCard(cp.getKey());
|
||||||
|
// Might cause issues since it ignores "Special" Cards
|
||||||
if (simpleCard == null) {
|
if (simpleCard == null) {
|
||||||
return TextUtil.concatWithSpace("contains the nonexisting card", cp.getKey());
|
return TextUtil.concatWithSpace("contains the nonexisting card", cp.getKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canHaveAnyNumberOf(simpleCard) && cp.getValue() > maxCopies) {
|
if (canHaveAnyNumberOf(simpleCard)) {
|
||||||
return TextUtil.concatWithSpace("must not contain more than", String.valueOf(maxCopies), "copies of the card", cp.getKey());
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Integer cardCopies = canHaveSpecificNumberInDeck(simpleCard);
|
||||||
|
if (cardCopies != null && deck.getMain().countByName(cp.getKey(), true) > cardCopies) {
|
||||||
|
return TextUtil.concatWithSpace("must not contain more than", String.valueOf(cardCopies), "copies of the card", cp.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cardCopies == null && cp.getValue() > maxCopies) {
|
||||||
|
return TextUtil.concatWithSpace("must not contain more than", String.valueOf(maxCopies), "copies of the card", cp.getKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,6 +373,16 @@ public enum DeckFormat {
|
|||||||
"A deck can have any number of cards named CARDNAME.");
|
"A deck can have any number of cards named CARDNAME.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Integer canHaveSpecificNumberInDeck(final IPaperCard card) {
|
||||||
|
// Ideally, this would be parsed during card parsing and set this value
|
||||||
|
if (Iterables.contains(card.getRules().getMainPart().getKeywords(),
|
||||||
|
"A deck can have up to seven cards named CARDNAME.")) {
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getPlaneSectionConformanceProblem(final CardPool planes) {
|
public static String getPlaneSectionConformanceProblem(final CardPool planes) {
|
||||||
//Must contain at least 10 planes/phenomenons, but max 2 phenomenons. Singleton.
|
//Must contain at least 10 planes/phenomenons, but max 2 phenomenons. Singleton.
|
||||||
if (planes == null || planes.countAll() < 10) {
|
if (planes == null || planes.countAll() < 10) {
|
||||||
@@ -442,6 +463,9 @@ public enum DeckFormat {
|
|||||||
if (this.equals(DeckFormat.Brawl)) {
|
if (this.equals(DeckFormat.Brawl)) {
|
||||||
return rules.canBeBrawlCommander();
|
return rules.canBeBrawlCommander();
|
||||||
}
|
}
|
||||||
|
if (this.equals(DeckFormat.TinyLeaders)) {
|
||||||
|
return rules.canBeTinyLeadersCommander();
|
||||||
|
}
|
||||||
return rules.canBeCommander();
|
return rules.canBeCommander();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class DeckSerializer {
|
|||||||
}
|
}
|
||||||
final List<String> metadata = map.get("metadata");
|
final List<String> metadata = map.get("metadata");
|
||||||
if (metadata != null) {
|
if (metadata != null) {
|
||||||
return new DeckFileHeader(FileSection.parse(metadata, "="));
|
return new DeckFileHeader(FileSection.parse(metadata, FileSection.EQUALS_KV_SEPARATOR));
|
||||||
}
|
}
|
||||||
final List<String> general = map.get("general");
|
final List<String> general = map.get("general");
|
||||||
if (general != null) {
|
if (general != null) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import forge.card.CardRarity;
|
|||||||
import forge.card.CardRules;
|
import forge.card.CardRules;
|
||||||
import forge.card.CardType.CoreType;
|
import forge.card.CardType.CoreType;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
|
import forge.util.PredicateCard;
|
||||||
import forge.util.PredicateString;
|
import forge.util.PredicateString;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
@@ -60,6 +61,8 @@ public interface IPaperCard extends InventoryItem {
|
|||||||
return new PredicateNames(what);
|
return new PredicateNames(what);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PredicateCards cards(final List<PaperCard> what) { return new PredicateCards(what); }
|
||||||
|
|
||||||
private static final class PredicateColor implements Predicate<PaperCard> {
|
private static final class PredicateColor implements Predicate<PaperCard> {
|
||||||
|
|
||||||
private final byte operand;
|
private final byte operand;
|
||||||
@@ -161,6 +164,25 @@ public interface IPaperCard extends InventoryItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class PredicateCards extends PredicateCard<PaperCard> {
|
||||||
|
private final List<PaperCard> operand;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(final PaperCard card) {
|
||||||
|
for (final PaperCard element : this.operand) {
|
||||||
|
if (this.op(card, element)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PredicateCards(final List<PaperCard> operand) {
|
||||||
|
super(StringOp.EQUALS);
|
||||||
|
this.operand = operand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pre-built predicates are stored here to allow their re-usage and
|
* Pre-built predicates are stored here to allow their re-usage and
|
||||||
* easier access from code.
|
* easier access from code.
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public class PreconDeck implements InventoryItemFromSet {
|
|||||||
|
|
||||||
// To be able to read "shops" section in overloads
|
// To be able to read "shops" section in overloads
|
||||||
protected PreconDeck getPreconDeckFromSections(final Map<String, List<String>> sections) {
|
protected PreconDeck getPreconDeckFromSections(final Map<String, List<String>> sections) {
|
||||||
FileSection kv = FileSection.parse(sections.get("metadata"), "=");
|
FileSection kv = FileSection.parse(sections.get("metadata"), FileSection.EQUALS_KV_SEPARATOR);
|
||||||
String imageFilename = kv.get("Image");
|
String imageFilename = kv.get("Image");
|
||||||
String description = kv.get("Description");
|
String description = kv.get("Description");
|
||||||
String deckEdition = kv.get("set");
|
String deckEdition = kv.get("set");
|
||||||
|
|||||||
@@ -566,12 +566,8 @@ public class BoosterGenerator {
|
|||||||
toAdd = IPaperCard.Predicates.printedInSets(sets);
|
toAdd = IPaperCard.Predicates.printedInSets(sets);
|
||||||
} else if (operator.startsWith("fromSheet(") && invert) {
|
} else if (operator.startsWith("fromSheet(") && invert) {
|
||||||
String sheetName = StringUtils.strip(operator.substring(9), "()\" ");
|
String sheetName = StringUtils.strip(operator.substring(9), "()\" ");
|
||||||
Iterable<PaperCard> src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
|
Iterable<PaperCard> cards = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
|
||||||
List<String> cardNames = Lists.newArrayList();
|
toAdd = IPaperCard.Predicates.cards(Lists.newArrayList(cards));
|
||||||
for (PaperCard card : src) {
|
|
||||||
cardNames.add(card.getName());
|
|
||||||
}
|
|
||||||
toAdd = IPaperCard.Predicates.names(Lists.newArrayList(cardNames));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toAdd == null) {
|
if (toAdd == null) {
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package forge.card;
|
package forge.util;
|
||||||
|
|
||||||
import com.esotericsoftware.minlog.Log;
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import forge.properties.ForgeConstants;
|
|
||||||
import forge.util.LineReader;
|
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -15,12 +12,12 @@ public class CardTranslation {
|
|||||||
private static Map <String, String> translatednames;
|
private static Map <String, String> translatednames;
|
||||||
private static Map <String, String> translatedtypes;
|
private static Map <String, String> translatedtypes;
|
||||||
private static Map <String, String> translatedoracles;
|
private static Map <String, String> translatedoracles;
|
||||||
private static String languageSelected;
|
private static String languageSelected = "en-US";
|
||||||
|
|
||||||
private static void readTranslationFile(String language) {
|
private static void readTranslationFile(String language, String languagesDirectory) {
|
||||||
String filename = "cardnames-" + language + ".txt";
|
String filename = "cardnames-" + language + ".txt";
|
||||||
|
|
||||||
try (LineReader translationFile = new LineReader(new FileInputStream(ForgeConstants.LANG_DIR + filename), Charsets.UTF_8)) {
|
try (LineReader translationFile = new LineReader(new FileInputStream(languagesDirectory + filename), Charsets.UTF_8)) {
|
||||||
for (String line : translationFile.readLines()) {
|
for (String line : translationFile.readLines()) {
|
||||||
String[] matches = line.split("\\|");
|
String[] matches = line.split("\\|");
|
||||||
if (matches.length >= 2) {
|
if (matches.length >= 2) {
|
||||||
@@ -34,7 +31,7 @@ public class CardTranslation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.error("Error reading translation file: cardnames-" + language + ".txt");
|
System.err.println("Error reading translation file: cardnames-" + language + ".txt");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +63,7 @@ public class CardTranslation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static HashMap<String, String> getTranslationTexts(String cardname, String altcardname) {
|
public static HashMap<String, String> getTranslationTexts(String cardname, String altcardname) {
|
||||||
HashMap<String, String> translations = new HashMap<String, String>();
|
HashMap<String, String> translations = new HashMap<>();
|
||||||
translations.put("name", getTranslatedName(cardname));
|
translations.put("name", getTranslatedName(cardname));
|
||||||
translations.put("oracle", getTranslatedOracle(cardname));
|
translations.put("oracle", getTranslatedOracle(cardname));
|
||||||
translations.put("altname", getTranslatedName(altcardname));
|
translations.put("altname", getTranslatedName(altcardname));
|
||||||
@@ -78,14 +75,14 @@ public class CardTranslation {
|
|||||||
return !languageSelected.equals("en-US");
|
return !languageSelected.equals("en-US");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void preloadTranslation(String language) {
|
public static void preloadTranslation(String language, String languagesDirectory) {
|
||||||
languageSelected = language;
|
languageSelected = language;
|
||||||
|
|
||||||
if (needsTranslation()) {
|
if (needsTranslation()) {
|
||||||
translatednames = new HashMap<>();
|
translatednames = new HashMap<>();
|
||||||
translatedtypes = new HashMap<>();
|
translatedtypes = new HashMap<>();
|
||||||
translatedoracles = new HashMap<>();
|
translatedoracles = new HashMap<>();
|
||||||
readTranslationFile(languageSelected);
|
readTranslationFile(languageSelected, languagesDirectory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,9 +17,12 @@
|
|||||||
*/
|
*/
|
||||||
package forge.util;
|
package forge.util;
|
||||||
|
|
||||||
|
import com.google.common.collect.HashBasedTable;
|
||||||
|
import com.google.common.collect.Table;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -57,31 +60,37 @@ public class FileSection {
|
|||||||
lines = lines0;
|
lines = lines0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static final Pattern DOLLAR_SIGN_KV_SEPARATOR = Pattern.compile(Pattern.quote("$"));
|
||||||
* Parses the.
|
public static final Pattern ARROW_KV_SEPARATOR = Pattern.compile(Pattern.quote("->"));
|
||||||
*
|
public static final Pattern EQUALS_KV_SEPARATOR = Pattern.compile(Pattern.quote("="));
|
||||||
* @param line the line
|
public static final Pattern COLON_KV_SEPARATOR = Pattern.compile(Pattern.quote(":"));
|
||||||
* @param kvSeparator the kv separator
|
|
||||||
* @param pairSeparator the pair separator
|
private static final String BAR_PAIR_SPLITTER = Pattern.quote("|");
|
||||||
* @return the file section
|
|
||||||
*/
|
private static Table<String, Pattern, Map<String, String>> parseToMapCache = HashBasedTable.create();
|
||||||
public static FileSection parse(final String line, final String kvSeparator, final String pairSeparator) {
|
|
||||||
Map<String, String> map = parseToMap(line, kvSeparator, pairSeparator);
|
public static Map<String, String> parseToMap(final String line, final Pattern kvSeparator) {
|
||||||
return new FileSection(map);
|
Map<String, String> result = parseToMapCache.get(line, kvSeparator);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result = parseToMapImpl(line, kvSeparator);
|
||||||
|
parseToMapCache.put(line, kvSeparator, result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<String, String> parseToMap(final String line, final String kvSeparator, final String pairSeparator) {
|
private static Map<String, String> parseToMapImpl(final String line, final Pattern kvSeparator) {
|
||||||
Map<String, String> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
if (StringUtils.isEmpty(line)) {
|
||||||
if (!StringUtils.isEmpty(line)) {
|
return Collections.emptyMap();
|
||||||
final String[] pairs = line.split(Pattern.quote(pairSeparator));
|
}
|
||||||
final Pattern splitter = Pattern.compile(Pattern.quote(kvSeparator));
|
|
||||||
|
|
||||||
|
final Map<String, String> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
final String[] pairs = line.split(BAR_PAIR_SPLITTER);
|
||||||
for (final String dd : pairs) {
|
for (final String dd : pairs) {
|
||||||
final String[] v = splitter.split(dd, 2);
|
final String[] v = kvSeparator.split(dd, 2);
|
||||||
result.put(v[0].trim(), v.length > 1 ? v[1].trim() : "");
|
result.put(v[0].trim(), v.length > 1 ? v[1].trim() : "");
|
||||||
}
|
}
|
||||||
}
|
return Collections.unmodifiableMap(result);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -91,11 +100,10 @@ public class FileSection {
|
|||||||
* @param kvSeparator the kv separator
|
* @param kvSeparator the kv separator
|
||||||
* @return the file section
|
* @return the file section
|
||||||
*/
|
*/
|
||||||
public static FileSection parse(final Iterable<String> lines, final String kvSeparator) {
|
public static FileSection parse(final Iterable<String> lines, final Pattern kvSeparator) {
|
||||||
final FileSection result = new FileSection();
|
final FileSection result = new FileSection();
|
||||||
final Pattern splitter = Pattern.compile(Pattern.quote(kvSeparator));
|
|
||||||
for (final String dd : lines) {
|
for (final String dd : lines) {
|
||||||
final String[] v = splitter.split(dd, 2);
|
final String[] v = kvSeparator.split(dd, 2);
|
||||||
result.lines.put(v[0].trim(), v.length > 1 ? v[1].trim() : "");
|
result.lines.put(v[0].trim(), v.length > 1 ? v[1].trim() : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user