mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 18:58:00 +00:00
Compare commits
3238 Commits
exiledWith
...
forge-1.6.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c768baf64 | ||
|
|
789ed51ca6 | ||
|
|
307c8e5dd1 | ||
|
|
4d4c427ca9 | ||
|
|
7838a9dfba | ||
|
|
8a64097a85 | ||
|
|
a8e11227f9 | ||
|
|
ea9001f582 | ||
|
|
bbdc74f9a4 | ||
|
|
2a7fa0f691 | ||
|
|
37de739a9c | ||
|
|
763d509638 | ||
|
|
32b4407b04 | ||
|
|
1d4f68d95f | ||
|
|
bb2c967f6e | ||
|
|
6ccb41cfa6 | ||
|
|
118e81d046 | ||
|
|
a146996c33 | ||
|
|
f6977fad35 | ||
|
|
9c71c7914b | ||
|
|
88e3f473d7 | ||
|
|
a44452f3c9 | ||
|
|
54569bffeb | ||
|
|
97fa6f1312 | ||
|
|
4201fa63db | ||
|
|
caf067ec68 | ||
|
|
606ded6fc2 | ||
|
|
18b3e14f2e | ||
|
|
80a24a1f93 | ||
|
|
53653d5cc6 | ||
|
|
e718903957 | ||
|
|
ca27221188 | ||
|
|
21e9dd3976 | ||
|
|
dcdf064e43 | ||
|
|
3103e4d575 | ||
|
|
ea3353ee2c | ||
|
|
4360cc7fd5 | ||
|
|
babd2deb71 | ||
|
|
7c657dadf1 | ||
|
|
f292ad2fb6 | ||
|
|
49b4db24ea | ||
|
|
904524ea6d | ||
|
|
2d16490aab | ||
|
|
048995fb47 | ||
|
|
aa46867e31 | ||
|
|
39e9434901 | ||
|
|
ead0d809cd | ||
|
|
db4ac4db9d | ||
|
|
b0c2421419 | ||
|
|
62ab654497 | ||
|
|
5c033655c7 | ||
|
|
5b4653754f | ||
|
|
8339745956 | ||
|
|
444bc8cfdd | ||
|
|
9886a16c6e | ||
|
|
d54df10ccd | ||
|
|
8f53b89b25 | ||
|
|
c24f3ed288 | ||
|
|
b9b71e8535 | ||
|
|
7421e59454 | ||
|
|
356baae747 | ||
|
|
471a9e1513 | ||
|
|
9ada642605 | ||
|
|
a634860ffd | ||
|
|
131fd8f7cf | ||
|
|
d280317ff8 | ||
|
|
a882f56b7e | ||
|
|
e79fbad31d | ||
|
|
5bd3062ce2 | ||
|
|
d3a464304d | ||
|
|
fa70d55432 | ||
|
|
873ff52224 | ||
|
|
87e6af657f | ||
|
|
7f4f0a18f6 | ||
|
|
8db5c771dc | ||
|
|
47d103f493 | ||
|
|
bf3379279d | ||
|
|
6d72415556 | ||
|
|
f824c33a85 | ||
|
|
ddf8eeb5c9 | ||
|
|
2c9b3f648d | ||
|
|
d5388b8831 | ||
|
|
a3a6bd20a5 | ||
|
|
90ffdcba11 | ||
|
|
1cd8255ab6 | ||
|
|
ab869853ac | ||
|
|
8bdf7fe388 | ||
|
|
e71c05a985 | ||
|
|
416f877100 | ||
|
|
e74e89b737 | ||
|
|
2ded193dda | ||
|
|
6a4c35272b | ||
|
|
45ca346703 | ||
|
|
0f431a21a6 | ||
|
|
1639a07e07 | ||
|
|
8bd1222b90 | ||
|
|
75f674cfca | ||
|
|
c2e4b8fab0 | ||
|
|
7a615c3a7c | ||
|
|
0114d8c9db | ||
|
|
9f39857dad | ||
|
|
32a6c7e001 | ||
|
|
07d71143b6 | ||
|
|
2873fe6dd3 | ||
|
|
b7b647746e | ||
|
|
426acedb76 | ||
|
|
72eb2b4409 | ||
|
|
1500729128 | ||
|
|
d53270514f | ||
|
|
c7e6076f8b | ||
|
|
d6aa6ac4c1 | ||
|
|
fd03367ac4 | ||
|
|
168cc31955 | ||
|
|
cb7d8e572c | ||
|
|
e0165ef99d | ||
|
|
5c2b16f35e | ||
|
|
ddb4bce2cc | ||
|
|
d46a529c74 | ||
|
|
d8b376e8b1 | ||
|
|
34c98119b3 | ||
|
|
1601afdfc0 | ||
|
|
a9f5ab8772 | ||
|
|
74038eefb5 | ||
|
|
04174c2cea | ||
|
|
fda99c3006 | ||
|
|
ac516168e4 | ||
|
|
71070e66cd | ||
|
|
d0d220330b | ||
|
|
a2f1b258a3 | ||
|
|
39bf803a92 | ||
|
|
4fecc76394 | ||
|
|
3445c65eb5 | ||
|
|
33377abc9f | ||
|
|
90fc581d9c | ||
|
|
46efa00f36 | ||
|
|
5ad8efe535 | ||
|
|
86ed6c7059 | ||
|
|
b17875b5fd | ||
|
|
358c6986e0 | ||
|
|
e8113dc203 | ||
|
|
7dd18e9a7b | ||
|
|
ce70015d08 | ||
|
|
b2fca7c27f | ||
|
|
048dd90003 | ||
|
|
39c7c18386 | ||
|
|
4980bb235c | ||
|
|
024db5c198 | ||
|
|
9148ca3806 | ||
|
|
7d4e9667c0 | ||
|
|
9bd67d1a17 | ||
|
|
788b34194a | ||
|
|
3b690d8c2a | ||
|
|
72e734faba | ||
|
|
69a65da77c | ||
|
|
c936b9fed9 | ||
|
|
35749300ce | ||
|
|
43cf318007 | ||
|
|
c213dc0ecd | ||
|
|
72c053c4b9 | ||
|
|
22fc15c3c8 | ||
|
|
da7225dc33 | ||
|
|
955e1552ca | ||
|
|
0822a4de0d | ||
|
|
c9dae7c666 | ||
|
|
09795207ff | ||
|
|
af5d8cd988 | ||
|
|
f705634b5c | ||
|
|
0bbe2828ef | ||
|
|
953430255e | ||
|
|
47ad20523e | ||
|
|
a14b13164f | ||
|
|
d903cb1a84 | ||
|
|
baa7855ff5 | ||
|
|
4e02f01bf8 | ||
|
|
87faa670e1 | ||
|
|
bd7ab26f3e | ||
|
|
d58191283e | ||
|
|
8dcf4b7a19 | ||
|
|
e029149893 | ||
|
|
57acff9bff | ||
|
|
f5ee502601 | ||
|
|
b07f97bf34 | ||
|
|
4bbf397f7a | ||
|
|
98c778b298 | ||
|
|
56ed95a94d | ||
|
|
f171a3be09 | ||
|
|
ab85ccaff9 | ||
|
|
ad55566a98 | ||
|
|
9a621f2179 | ||
|
|
a00e408c84 | ||
|
|
40ef4de16e | ||
|
|
f5bf7a4581 | ||
|
|
ef7ff4dd66 | ||
|
|
e645dd3eaf | ||
|
|
3f30f45972 | ||
|
|
26adc5ca7b | ||
|
|
4e99063750 | ||
|
|
3a1bd57fcb | ||
|
|
106515d217 | ||
|
|
70531a8f31 | ||
|
|
6562a04667 | ||
|
|
4decf8bf64 | ||
|
|
bb65d4d2ec | ||
|
|
119a53b0fd | ||
|
|
c9036f90ef | ||
|
|
0cb35f7c84 | ||
|
|
3e3f0729a9 | ||
|
|
8f70a6ab96 | ||
|
|
5f39b98cc5 | ||
|
|
d8dfbcf1a3 | ||
|
|
13217aaa3e | ||
|
|
da6162180e | ||
|
|
175db96e4f | ||
|
|
64e95df6a2 | ||
|
|
d10a1e1a78 | ||
|
|
de4ae7bb3c | ||
|
|
f0ead44849 | ||
|
|
f3ecd98f8a | ||
|
|
7eec98db61 | ||
|
|
5de3bab789 | ||
|
|
9c57f10461 | ||
|
|
91e8c24769 | ||
|
|
20bc46c0fd | ||
|
|
ed8e5e3f2e | ||
|
|
ab16bd9c19 | ||
|
|
114d23a6b8 | ||
|
|
f1054cf026 | ||
|
|
2ff040d7d4 | ||
|
|
0d49de243f | ||
|
|
329b5953cc | ||
|
|
5ae43b65ff | ||
|
|
6e831a262d | ||
|
|
e6c5c8989c | ||
|
|
ef00ba8a8a | ||
|
|
9815acfbdb | ||
|
|
4ce37bcaf8 | ||
|
|
133262a920 | ||
|
|
572cc75866 | ||
|
|
fee731edde | ||
|
|
dd3d26191a | ||
|
|
77d9575e53 | ||
|
|
27856e65a0 | ||
|
|
e75bdd2d5f | ||
|
|
cd4d0a3be1 | ||
|
|
8f4d58d762 | ||
|
|
da86b24155 | ||
|
|
7e8b9c0b5a | ||
|
|
c3d276f3a9 | ||
|
|
a2a2c11e4b | ||
|
|
8caf3fdcbf | ||
|
|
c92d1812fd | ||
|
|
90791a7184 | ||
|
|
ad9371b4af | ||
|
|
8a80a39416 | ||
|
|
6dcad6fad0 | ||
|
|
ba7f08d5ec | ||
|
|
222448d29c | ||
|
|
377cbca39b | ||
|
|
97225782c1 | ||
|
|
8b96079f6e | ||
|
|
921369628b | ||
|
|
2480296113 | ||
|
|
3b357207a4 | ||
|
|
ac6219f4bd | ||
|
|
c70ab7b722 | ||
|
|
004ea04a0e | ||
|
|
2b342d7118 | ||
|
|
5645f2212e | ||
|
|
e5cb062644 | ||
|
|
4bce4a9a84 | ||
|
|
8a6a0c5d85 | ||
|
|
da0f7ed15e | ||
|
|
6210fdd9fc | ||
|
|
610fe7fada | ||
|
|
4108b56135 | ||
|
|
1534636a64 | ||
|
|
94da5f3c3a | ||
|
|
a8205192ca | ||
|
|
22337e4a7b | ||
|
|
ffb40f1195 | ||
|
|
df160a751e | ||
|
|
911511d039 | ||
|
|
ef917a91b9 | ||
|
|
589f714d70 | ||
|
|
b28e32460d | ||
|
|
2fffb11d72 | ||
|
|
7dcf2c6131 | ||
|
|
49fe438790 | ||
|
|
f559bd98ef | ||
|
|
a3372980e5 | ||
|
|
a42d64353e | ||
|
|
445826e50e | ||
|
|
998873f311 | ||
|
|
85b2d97a4c | ||
|
|
eaa6d6acd6 | ||
|
|
d1c0d7f49b | ||
|
|
20712cc652 | ||
|
|
ebcfa5b084 | ||
|
|
cb2d44a35c | ||
|
|
62ef92364f | ||
|
|
9c38466c33 | ||
|
|
b28526cfe3 | ||
|
|
5d9e0f54a6 | ||
|
|
10645c4da8 | ||
|
|
28ab74ce06 | ||
|
|
0c745c51fd | ||
|
|
e1f3293126 | ||
|
|
fed5b9af3d | ||
|
|
09a4d6f4e2 | ||
|
|
66d37c324f | ||
|
|
ff1cd7d01b | ||
|
|
1665d3ec67 | ||
|
|
b7534d3c97 | ||
|
|
21e16e24be | ||
|
|
85e8446f32 | ||
|
|
47337a8414 | ||
|
|
6e41ad9288 | ||
|
|
b4928378c9 | ||
|
|
996d6011f4 | ||
|
|
4b5eb736e0 | ||
|
|
4c08a961d7 | ||
|
|
fcf645e8d0 | ||
|
|
3b3e4a3d42 | ||
|
|
b363db47bc | ||
|
|
dbf8ffd4a6 | ||
|
|
d883bdd67f | ||
|
|
e64655280c | ||
|
|
512bf18381 | ||
|
|
df8de75a28 | ||
|
|
d36d4398ac | ||
|
|
08ad1e4189 | ||
|
|
adb7cd643e | ||
|
|
da7d20ea60 | ||
|
|
d0c1d730d6 | ||
|
|
a2c85718b9 | ||
|
|
7deffd8b7b | ||
|
|
06cad4e182 | ||
|
|
02afdc5a29 | ||
|
|
60d98913eb | ||
|
|
f050f572ec | ||
|
|
3c9462ff34 | ||
|
|
719eb4f64f | ||
|
|
ec08814230 | ||
|
|
9c9531ff8c | ||
|
|
9cff9ebf98 | ||
|
|
890034da1d | ||
|
|
d878569ac1 | ||
|
|
88df48fffe | ||
|
|
165bf3ac6f | ||
|
|
6a109b64d2 | ||
|
|
d396af5e2f | ||
|
|
a7d8e9fde3 | ||
|
|
db3bf2ee6e | ||
|
|
a1a326adc6 | ||
|
|
c832116ea8 | ||
|
|
590ab2c93d | ||
|
|
bc854a0572 | ||
|
|
81838f4b21 | ||
|
|
fc7c01013b | ||
|
|
c7bad2bbd8 | ||
|
|
9eaa836e7d | ||
|
|
e46e1af8c5 | ||
|
|
fbb1c906ea | ||
|
|
959ed6ed23 | ||
|
|
c747957cf3 | ||
|
|
72b9ad8651 | ||
|
|
716503ea38 | ||
|
|
163aa7edbe | ||
|
|
2365e61256 | ||
|
|
a9b82b1eba | ||
|
|
6f1fe2d0ee | ||
|
|
7636c35fb5 | ||
|
|
7a78432976 | ||
|
|
efa9d9138d | ||
|
|
5099978ee0 | ||
|
|
6c89f2053e | ||
|
|
47917e4a3f | ||
|
|
b59778b59f | ||
|
|
e2af4da0d5 | ||
|
|
6505e5562f | ||
|
|
01a1885289 | ||
|
|
ee63f606f2 | ||
|
|
6ff0548dc8 | ||
|
|
80645fb9c3 | ||
|
|
8aee6d8e85 | ||
|
|
789af729b2 | ||
|
|
95d459d0e6 | ||
|
|
44f518db90 | ||
|
|
259a84561d | ||
|
|
0d7fb25471 | ||
|
|
a26fef98eb | ||
|
|
c0fc97e81f | ||
|
|
b29f744b45 | ||
|
|
e753d45b42 | ||
|
|
52160c9e60 | ||
|
|
71ff068101 | ||
|
|
13214a04a5 | ||
|
|
9c7704c6bd | ||
|
|
ca7b05c472 | ||
|
|
f5aee89476 | ||
|
|
7180295a1f | ||
|
|
0b3def285a | ||
|
|
6b3ab5edf9 | ||
|
|
4dda484173 | ||
|
|
20387ffce0 | ||
|
|
3665ca5c2b | ||
|
|
eb8ac6336a | ||
|
|
d3487231f5 | ||
|
|
6996a172e8 | ||
|
|
f05ede87fc | ||
|
|
f14de1c2c1 | ||
|
|
0188463b7e | ||
|
|
6e2dd8f267 | ||
|
|
6d8be55032 | ||
|
|
5e7adf6383 | ||
|
|
d89bff8554 | ||
|
|
cafdc40348 | ||
|
|
bc4e9ef4ac | ||
|
|
d6d66e782f | ||
|
|
b5990e92db | ||
|
|
33d969db8f | ||
|
|
f4dcf10e41 | ||
|
|
7599a24ed9 | ||
|
|
83554b032f | ||
|
|
2b12d56f17 | ||
|
|
77a73c04a4 | ||
|
|
26364aaf3f | ||
|
|
80ae0484b8 | ||
|
|
ca65ff229f | ||
|
|
38298d4b60 | ||
|
|
d6c180e0da | ||
|
|
a54bb82c84 | ||
|
|
9a5fdce071 | ||
|
|
da338f02c3 | ||
|
|
07a8f78c27 | ||
|
|
09774ae13f | ||
|
|
764a77b69e | ||
|
|
2bb3fdf088 | ||
|
|
de8a4c9df8 | ||
|
|
250daad3f4 | ||
|
|
d196c06c4c | ||
|
|
d99eb6bed6 | ||
|
|
20bddf5bb4 | ||
|
|
0b664c4278 | ||
|
|
0969b896c8 | ||
|
|
3d817ab16a | ||
|
|
8a6814ce67 | ||
|
|
61f1a2c35c | ||
|
|
01d4d6b22e | ||
|
|
e757af41f7 | ||
|
|
e8ccc34106 | ||
|
|
ce7c10b37b | ||
|
|
77e29bac0b | ||
|
|
2ec99267da | ||
|
|
6b623f8c7f | ||
|
|
d7badae5ea | ||
|
|
bec85590c5 | ||
|
|
d43c1c023e | ||
|
|
ecb321a584 | ||
|
|
8ac9e7a383 | ||
|
|
c327e4eec6 | ||
|
|
2cb0df9422 | ||
|
|
fb4d21a2cc | ||
|
|
68f3c9a686 | ||
|
|
ac78f5fa56 | ||
|
|
e4ce75036f | ||
|
|
ff2e67679f | ||
|
|
60e2fce2f3 | ||
|
|
48b142c7a4 | ||
|
|
a30438e853 | ||
|
|
a9c92da5dd | ||
|
|
ed3e6abe9c | ||
|
|
8f4a83e44a | ||
|
|
ddae3d97a4 | ||
|
|
f82c7eb220 | ||
|
|
3b9d63221c | ||
|
|
099970de6a | ||
|
|
22e2edae1a | ||
|
|
dbdb481a2c | ||
|
|
91d16eaa2d | ||
|
|
00cb0db6a2 | ||
|
|
3d83ae70a3 | ||
|
|
c0ba652956 | ||
|
|
e8646764f3 | ||
|
|
b332fb1934 | ||
|
|
62820e920e | ||
|
|
b30f797964 | ||
|
|
7cebead3bf | ||
|
|
5639e1639a | ||
|
|
7d7c4ae3ae | ||
|
|
2daa0ddeaa | ||
|
|
755d2d0605 | ||
|
|
1b8f73b11d | ||
|
|
a946178ad3 | ||
|
|
340dee6abb | ||
|
|
a5852b76f0 | ||
|
|
f7e4ce221b | ||
|
|
aaf5bd9fff | ||
|
|
ec122b12b9 | ||
|
|
8e645d40aa | ||
|
|
cae3a53825 | ||
|
|
c0c258f03a | ||
|
|
8d8f0dbb3b | ||
|
|
fa86c351d4 | ||
|
|
cf5c96d717 | ||
|
|
5df34731b5 | ||
|
|
315dea3994 | ||
|
|
8ec4a69cb0 | ||
|
|
2a88d598e0 | ||
|
|
efa14919aa | ||
|
|
716ac11bef | ||
|
|
63d7b7fda5 | ||
|
|
7d94f423d4 | ||
|
|
1fb50f416c | ||
|
|
1211f41939 | ||
|
|
aa1f46d916 | ||
|
|
35f2c7dacf | ||
|
|
d76efc5d4f | ||
|
|
3cff521da0 | ||
|
|
e5258072d2 | ||
|
|
9feaf90637 | ||
|
|
553b7305a2 | ||
|
|
5f5aad4a25 | ||
|
|
21863f5c78 | ||
|
|
4e0130f9e0 | ||
|
|
5f742d59b6 | ||
|
|
24768d42b3 | ||
|
|
2bf31bfae6 | ||
|
|
792d4759ca | ||
|
|
64271eda4c | ||
|
|
f38c902c9c | ||
|
|
0885fc0995 | ||
|
|
14fe4b862f | ||
|
|
d655d43910 | ||
|
|
de20cec586 | ||
|
|
cd05a8ffe1 | ||
|
|
acc6941220 | ||
|
|
3b8b28c355 | ||
|
|
0ac9e33dc7 | ||
|
|
94ae1fe777 | ||
|
|
6f3b578fc0 | ||
|
|
4598c67578 | ||
|
|
1e93f31d0b | ||
|
|
9de6a40028 | ||
|
|
1cd5b6633f | ||
|
|
65e6a01785 | ||
|
|
f5561a17f9 | ||
|
|
a3d5094175 | ||
|
|
82a9582f34 | ||
|
|
7846128ba5 | ||
|
|
4b542b9002 | ||
|
|
da4e428cb8 | ||
|
|
0c6d965a1c | ||
|
|
cfa2432cca | ||
|
|
0c61ae7a2b | ||
|
|
9b8654a1af | ||
|
|
ecf3dcca99 | ||
|
|
02c76474c8 | ||
|
|
f8832a19c0 | ||
|
|
413352a4d5 | ||
|
|
fcd7fe840d | ||
|
|
2843bc18fc | ||
|
|
478b8e604f | ||
|
|
b9554604b6 | ||
|
|
aec56d726d | ||
|
|
863d1d4884 | ||
|
|
f2cfeb31b8 | ||
|
|
ea50d8f947 | ||
|
|
ee835d310a | ||
|
|
4567260d61 | ||
|
|
d0a4ff54f7 | ||
|
|
767b6f1ea5 | ||
|
|
4eb2bcca89 | ||
|
|
6b60e478ee | ||
|
|
ecd7afdf4a | ||
|
|
31beff4f4f | ||
|
|
642915a798 | ||
|
|
6cdc5c0cf8 | ||
|
|
eb20ff0a2a | ||
|
|
657eda2062 | ||
|
|
a6bf79500d | ||
|
|
d4ff181a1f | ||
|
|
e516b00fa9 | ||
|
|
6138235b8b | ||
|
|
fd54ecb571 | ||
|
|
eec8b7a42a | ||
|
|
ccfe318363 | ||
|
|
a83c06baf0 | ||
|
|
c7eff32f94 | ||
|
|
e67402d5ae | ||
|
|
81e35ca7f7 | ||
|
|
72f3411242 | ||
|
|
75f04de148 | ||
|
|
2189a33171 | ||
|
|
72e40504d7 | ||
|
|
811d6479f5 | ||
|
|
e6958305b5 | ||
|
|
2808b4945e | ||
|
|
99dcd03e64 | ||
|
|
5cf3458a5e | ||
|
|
55e2cdddb7 | ||
|
|
b130e7f658 | ||
|
|
b6dfbd5e2c | ||
|
|
79bb2639a8 | ||
|
|
ba2a48600c | ||
|
|
cc8c58c961 | ||
|
|
73562528f6 | ||
|
|
cbd8f0297c | ||
|
|
f48fa6baef | ||
|
|
83eb0f8ee4 | ||
|
|
4c8acb251e | ||
|
|
39a0b9b543 | ||
|
|
4b785fd75b | ||
|
|
c491f6dfb6 | ||
|
|
2e55b6c884 | ||
|
|
25d30254a1 | ||
|
|
028307953b | ||
|
|
d4b0c648e9 | ||
|
|
4fe67ccb8c | ||
|
|
6ff7ccd3ac | ||
|
|
800a43eba4 | ||
|
|
a2ed1c386f | ||
|
|
a8cba0eef4 | ||
|
|
e0f01a3168 | ||
|
|
a132e9a3d4 | ||
|
|
d751236154 | ||
|
|
32e3d00cec | ||
|
|
b800e478f1 | ||
|
|
f655151a6e | ||
|
|
5d6939b2f2 | ||
|
|
30d276aa29 | ||
|
|
3dd44ba71a | ||
|
|
33f5080ca6 | ||
|
|
affbd917b3 | ||
|
|
3126d7ac5d | ||
|
|
1f0e49d1d4 | ||
|
|
60d4991a84 | ||
|
|
d5149293da | ||
|
|
75ccabff9c | ||
|
|
dd6dc739fb | ||
|
|
ac094f4af4 | ||
|
|
cdb2330792 | ||
|
|
7744552450 | ||
|
|
6de4789428 | ||
|
|
3af8a7dfa4 | ||
|
|
9e92e46cad | ||
|
|
962d5c2105 | ||
|
|
ef4536d033 | ||
|
|
01376d82db | ||
|
|
570abfcd21 | ||
|
|
5bf0ad547e | ||
|
|
f8a256ea27 | ||
|
|
e46001cc91 | ||
|
|
07261e8525 | ||
|
|
64f022f809 | ||
|
|
c07a63cd7e | ||
|
|
60b27803ce | ||
|
|
c5c58885e6 | ||
|
|
0c748c091e | ||
|
|
8c8c8ba0c7 | ||
|
|
2e03b172b2 | ||
|
|
6474f2a7eb | ||
|
|
51df73d918 | ||
|
|
98cf75e92e | ||
|
|
43ac1fabcf | ||
|
|
f06fa47e75 | ||
|
|
06c60a2540 | ||
|
|
8bf557cc19 | ||
|
|
00e256edf7 | ||
|
|
cd7bd9263a | ||
|
|
933d92bdd4 | ||
|
|
101ce59f06 | ||
|
|
3e16390999 | ||
|
|
4c71f76260 | ||
|
|
c7cb42cd7b | ||
|
|
a1964eb75e | ||
|
|
fb0c44fce6 | ||
|
|
3f263fefee | ||
|
|
d7b252185a | ||
|
|
2cc6128f1f | ||
|
|
da6c027315 | ||
|
|
e4f7bd1f68 | ||
|
|
5de6401ed8 | ||
|
|
2e469be484 | ||
|
|
2074320961 | ||
|
|
626124c912 | ||
|
|
2bf066b937 | ||
|
|
8de27ddb0c | ||
|
|
030ed46cae | ||
|
|
b85b067e42 | ||
|
|
74b439adaf | ||
|
|
d020e8a7d1 | ||
|
|
27624ae6e2 | ||
|
|
10bbd4d07f | ||
|
|
a2fa475c43 | ||
|
|
9965517329 | ||
|
|
9469f1bdaf | ||
|
|
5163836668 | ||
|
|
322008b458 | ||
|
|
059641f71d | ||
|
|
b383262e70 | ||
|
|
636cbaeb5f | ||
|
|
866602a683 | ||
|
|
0638670b7c | ||
|
|
9df082f448 | ||
|
|
66e8cc5bff | ||
|
|
7188f2ccb3 | ||
|
|
28cd8c877d | ||
|
|
ccd2f2c1c6 | ||
|
|
bda997ef1e | ||
|
|
6849578899 | ||
|
|
96785e1353 | ||
|
|
a02f29ba8a | ||
|
|
76e361287a | ||
|
|
2f9e2dc2ef | ||
|
|
a3b685a937 | ||
|
|
9543534dda | ||
|
|
af0056d971 | ||
|
|
cec46460f4 | ||
|
|
789fb973a9 | ||
|
|
1a38a62941 | ||
|
|
a52df6e4d1 | ||
|
|
82ce6d1e3d | ||
|
|
f71804a56f | ||
|
|
5f0662c139 | ||
|
|
c70b3ba4ae | ||
|
|
4fbe9084e2 | ||
|
|
2381589bea | ||
|
|
fcdb9b25bd | ||
|
|
6cc745526b | ||
|
|
745a33ef8c | ||
|
|
dc54a94bcc | ||
|
|
eb519f4278 | ||
|
|
934c780b73 | ||
|
|
dc0ed6e233 | ||
|
|
9245841d4d | ||
|
|
49a85dbe55 | ||
|
|
e2b7fe37aa | ||
|
|
251ffbf574 | ||
|
|
0d01aeaceb | ||
|
|
9fa1b6e585 | ||
|
|
d3dded4759 | ||
|
|
ae32a9f27b | ||
|
|
80bc6785ba | ||
|
|
622fbe3192 | ||
|
|
7516882c0d | ||
|
|
7a1a657ae9 | ||
|
|
0f4af36c17 | ||
|
|
70677a963a | ||
|
|
41c3d6ebed | ||
|
|
bdff72745a | ||
|
|
b57e3e89bf | ||
|
|
974b027cf6 | ||
|
|
7d884eb61e | ||
|
|
883d7ce476 | ||
|
|
921fc43f75 | ||
|
|
42223f109e | ||
|
|
4c264b7e3d | ||
|
|
dab893c24f | ||
|
|
a276118409 | ||
|
|
57c81ec49f | ||
|
|
411e77c8c6 | ||
|
|
af12354366 | ||
|
|
81ec996ab3 | ||
|
|
756132c8af | ||
|
|
f7694970c8 | ||
|
|
8ca4432482 | ||
|
|
b51436d4f1 | ||
|
|
dc793aa266 | ||
|
|
cabfe0a33a | ||
|
|
d22d3f21da | ||
|
|
6e2d5c13b4 | ||
|
|
1252b25b0a | ||
|
|
4372311297 | ||
|
|
5b67c08ae7 | ||
|
|
f0c1062c21 | ||
|
|
28db6218b4 | ||
|
|
146f44f062 | ||
|
|
8977303c00 | ||
|
|
96fa129285 | ||
|
|
1aa7825c52 | ||
|
|
9256fdfd1a | ||
|
|
0ef7986e3a | ||
|
|
8a7848f411 | ||
|
|
db3accfa50 | ||
|
|
a2e97700df | ||
|
|
9d80ae2d5c | ||
|
|
af04a994bb | ||
|
|
33526bb31b | ||
|
|
9c72cf2eb4 | ||
|
|
81b214f6ed | ||
|
|
eed49554b5 | ||
|
|
5cb384afa2 | ||
|
|
ef18dac22c | ||
|
|
8f01f56a1e | ||
|
|
d8cb67a5f6 | ||
|
|
a08df2b69d | ||
|
|
4d69748c9f | ||
|
|
12be76899a | ||
|
|
b982620402 | ||
|
|
9a58359563 | ||
|
|
233ad71b86 | ||
|
|
bb9cf72912 | ||
|
|
3906c1f3a5 | ||
|
|
a5b256f2fb | ||
|
|
12902e8b27 | ||
|
|
0393c8d4d9 | ||
|
|
37d7ef3ee9 | ||
|
|
1417bf436d | ||
|
|
35121c85af | ||
|
|
c9f0d81d29 | ||
|
|
88bba9e7c7 | ||
|
|
8764a4d33d | ||
|
|
a6d306cb36 | ||
|
|
4f0791aac0 | ||
|
|
09e778daeb | ||
|
|
8e3644a0f6 | ||
|
|
c3db51b5f8 | ||
|
|
9af01ed521 | ||
|
|
063cbbfd8a | ||
|
|
ef79f05db3 | ||
|
|
8562f2f28d | ||
|
|
3e72125163 | ||
|
|
3e007065b0 | ||
|
|
31fb70f115 | ||
|
|
7e0e24ddd7 | ||
|
|
c52a94bbfc | ||
|
|
80aa06cd92 | ||
|
|
dcd42ef387 | ||
|
|
7da17c3a36 | ||
|
|
88afa85e88 | ||
|
|
16c7add22c | ||
|
|
82339213a8 | ||
|
|
36caca89b6 | ||
|
|
3ced2f2a24 | ||
|
|
a12848864b | ||
|
|
e2785d4180 | ||
|
|
1421e1617d | ||
|
|
a45cbff98b | ||
|
|
270c7e3571 | ||
|
|
1b3cf98372 | ||
|
|
b647a74469 | ||
|
|
31d0d4535e | ||
|
|
e15c4be102 | ||
|
|
846b6de9ab | ||
|
|
673f26cab2 | ||
|
|
ffb05ed1b6 | ||
|
|
c4eb1ceb10 | ||
|
|
21e27534f8 | ||
|
|
b874c202cb | ||
|
|
de163b31d8 | ||
|
|
b5d93cabd3 | ||
|
|
0d30d8659a | ||
|
|
b5bc614c65 | ||
|
|
ac23ad25fc | ||
|
|
510063bd98 | ||
|
|
83e65d3b4d | ||
|
|
2c5548bc88 | ||
|
|
71ab3310da | ||
|
|
73929b3e1a | ||
|
|
55536c6480 | ||
|
|
b59ef4e43e | ||
|
|
42696363d3 | ||
|
|
85d6dce7f4 | ||
|
|
8b66ab3600 | ||
|
|
c5e9e0e703 | ||
|
|
15852e2c01 | ||
|
|
ed0c5aac5e | ||
|
|
59e82a33ab | ||
|
|
54cbd32229 | ||
|
|
ec33e028f6 | ||
|
|
be40fce2c6 | ||
|
|
0d9590f12a | ||
|
|
04403d12d7 | ||
|
|
96fcd3decd | ||
|
|
5e79ff5a20 | ||
|
|
9487d84e06 | ||
|
|
6e2a7904bb | ||
|
|
a098e659a8 | ||
|
|
c5eaef01ce | ||
|
|
6712a25bfc | ||
|
|
bed2d0bddc | ||
|
|
ae9e9c5201 | ||
|
|
44910573de | ||
|
|
4876fb0212 | ||
|
|
9eabe65496 | ||
|
|
9681ce5f9b | ||
|
|
8ea07e67f8 | ||
|
|
f281287c69 | ||
|
|
e5bdcead2a | ||
|
|
6b22266602 | ||
|
|
ad41eec543 | ||
|
|
1af68d9212 | ||
|
|
dc886b139a | ||
|
|
806d8f35c2 | ||
|
|
b17e88a80c | ||
|
|
2619cca56f | ||
|
|
2c2f546084 | ||
|
|
ea138b8c36 | ||
|
|
5ba6f29ebb | ||
|
|
79f4595292 | ||
|
|
fe0441c667 | ||
|
|
3ba464665e | ||
|
|
844360fb34 | ||
|
|
ee7ac95b6a | ||
|
|
5c46556537 | ||
|
|
70a6a9bda5 | ||
|
|
a5c30cadb4 | ||
|
|
fcdc8d691d | ||
|
|
6daf4ac78d | ||
|
|
ad72b44e12 | ||
|
|
d395af122c | ||
|
|
cb0259c946 | ||
|
|
6e5d79a8e0 | ||
|
|
7d8ea744c3 | ||
|
|
8a96bbb3d4 | ||
|
|
36e2c16c9d | ||
|
|
8928c9eb88 | ||
|
|
0de9a0dfd6 | ||
|
|
4ad137a23d | ||
|
|
f9651796f8 | ||
|
|
ce02772161 | ||
|
|
84f2d2ead2 | ||
|
|
d232cefe57 | ||
|
|
84882cd4fa | ||
|
|
0d592c4096 | ||
|
|
c7d7130344 | ||
|
|
7672817a6e | ||
|
|
b23e550263 | ||
|
|
b1f13cf2f0 | ||
|
|
afeedf064e | ||
|
|
9a21f038ad | ||
|
|
afa6c9986a | ||
|
|
c7e489b477 | ||
|
|
bd060e9090 | ||
|
|
dfecf5d40a | ||
|
|
db3919ab90 | ||
|
|
5193447625 | ||
|
|
42c5b890a7 | ||
|
|
31192889d1 | ||
|
|
b5e55a3a47 | ||
|
|
5e39a2979b | ||
|
|
be3c26676e | ||
|
|
20dc234ec1 | ||
|
|
cd29194824 | ||
|
|
777514dc7d | ||
|
|
28ee56baea | ||
|
|
cc362cec5c | ||
|
|
b698b49256 | ||
|
|
0b74be743a | ||
|
|
d844df988f | ||
|
|
410ceb2bfc | ||
|
|
da1dbfc9cf | ||
|
|
06fa495b0c | ||
|
|
21d5aeca09 | ||
|
|
b1134ef070 | ||
|
|
e8b6e066e3 | ||
|
|
c7d7f5c622 | ||
|
|
50221ef138 | ||
|
|
d59d5b3b46 | ||
|
|
6fadafa19a | ||
|
|
400eb62c76 | ||
|
|
68d78acc00 | ||
|
|
b3fe2a55db | ||
|
|
9697d697bd | ||
|
|
b1b06dd0f4 | ||
|
|
9dada96de3 | ||
|
|
93d80b3ee5 | ||
|
|
f584c581d8 | ||
|
|
001106ea7b | ||
|
|
6f14d1d37a | ||
|
|
43c39ec231 | ||
|
|
9da49cfce1 | ||
|
|
a3e302eee1 | ||
|
|
5c8fbd3c7e | ||
|
|
414aa96463 | ||
|
|
4a25ad9c6a | ||
|
|
56eda948d6 | ||
|
|
4df7433dfa | ||
|
|
9cb95f8f35 | ||
|
|
1c0c0ad38d | ||
|
|
84af4d6344 | ||
|
|
d92e2123a2 | ||
|
|
f447ab64ce | ||
|
|
404840de61 | ||
|
|
4049a3eea3 | ||
|
|
36f3165688 | ||
|
|
b79fc195a1 | ||
|
|
78bc621f3c | ||
|
|
b8aaf8fb68 | ||
|
|
cd4ce0d3ab | ||
|
|
32891863ad | ||
|
|
77d88b66e8 | ||
|
|
ff801fe747 | ||
|
|
73016d1fe0 | ||
|
|
733962a935 | ||
|
|
3f3a90b360 | ||
|
|
ec739b40de | ||
|
|
50d3e8cef4 | ||
|
|
fa095007cf | ||
|
|
db3bde4bbb | ||
|
|
37bb9279f1 | ||
|
|
4e1c6cb678 | ||
|
|
7e1fd332e0 | ||
|
|
58f369e6e5 | ||
|
|
368c2f9796 | ||
|
|
d658df35d6 | ||
|
|
4b6edee3e6 | ||
|
|
91ae1036de | ||
|
|
c20165df92 | ||
|
|
9bdd4b9351 | ||
|
|
c1efe047a3 | ||
|
|
13e57e25f6 | ||
|
|
5345bee294 | ||
|
|
e9b0902fe0 | ||
|
|
de21053398 | ||
|
|
27c993831e | ||
|
|
3dddca9c1e | ||
|
|
0effbb805a | ||
|
|
20225a78cb | ||
|
|
c8cedbd0af | ||
|
|
844098a302 | ||
|
|
08454489b4 | ||
|
|
71110568fc | ||
|
|
9990f0ad1a | ||
|
|
735a966312 | ||
|
|
828d4e17c9 | ||
|
|
233c1f6d00 | ||
|
|
dbe1683ba9 | ||
|
|
a5b7f93971 | ||
|
|
d853ca6615 | ||
|
|
418d2bf012 | ||
|
|
030f1f4056 | ||
|
|
45f598e0b9 | ||
|
|
fc82dbb9aa | ||
|
|
6feb18c11c | ||
|
|
c07f3ea34d | ||
|
|
8948210e0a | ||
|
|
df0b5362c2 | ||
|
|
a9b60a87e0 | ||
|
|
694317a50f | ||
|
|
a856834693 | ||
|
|
35b71da140 | ||
|
|
3b2e39808e | ||
|
|
697a2ace20 | ||
|
|
c38c53d513 | ||
|
|
31745fe31e | ||
|
|
f8e3f4dd31 | ||
|
|
965ec4bc47 | ||
|
|
9cf28e7041 | ||
|
|
71f639d45e | ||
|
|
084476b8b0 | ||
|
|
f9abb8ca75 | ||
|
|
4b0c748eaf | ||
|
|
083674071b | ||
|
|
96ccf0afc6 | ||
|
|
cfb755ce20 | ||
|
|
cc8753b563 | ||
|
|
454c51f1b1 | ||
|
|
fa83326710 | ||
|
|
eb60690973 | ||
|
|
0983c72ea0 | ||
|
|
49436a00b3 | ||
|
|
8345bf890b | ||
|
|
5b2980562b | ||
|
|
b86249635f | ||
|
|
4286429402 | ||
|
|
6578fb79e6 | ||
|
|
f1169511ab | ||
|
|
6f5f04bb7e | ||
|
|
b33595d840 | ||
|
|
653b484674 | ||
|
|
5a0a63940d | ||
|
|
d734fdf761 | ||
|
|
3ab7fcf224 | ||
|
|
4f4fe04683 | ||
|
|
c83d03a2d6 | ||
|
|
8ffa95a26a | ||
|
|
da96b580a4 | ||
|
|
5a862ab742 | ||
|
|
7e6f077b0a | ||
|
|
b6676c9d58 | ||
|
|
37099156f3 | ||
|
|
018a2c080b | ||
|
|
890ef37b3f | ||
|
|
c7de3866c7 | ||
|
|
4cf11743f2 | ||
|
|
3ee3a28fd5 | ||
|
|
39ba048d07 | ||
|
|
40a9229681 | ||
|
|
8a7873d0c9 | ||
|
|
996ddb4360 | ||
|
|
eec11c6ec5 | ||
|
|
197cbb1808 | ||
|
|
32df7f3eae | ||
|
|
4b3839b143 | ||
|
|
79216eaa8d | ||
|
|
ba2998541d | ||
|
|
d5aaa4dc45 | ||
|
|
999e4f81ee | ||
|
|
c38d2f531c | ||
|
|
27d8af3551 | ||
|
|
b10ce1b884 | ||
|
|
18c207f39b | ||
|
|
898df454d5 | ||
|
|
577c5ca055 | ||
|
|
04558e93b3 | ||
|
|
6b0a88cd95 | ||
|
|
bd125df278 | ||
|
|
fc31881150 | ||
|
|
9a13b06452 | ||
|
|
01e3a726fe | ||
|
|
bcaa318941 | ||
|
|
c64e5c2d5e | ||
|
|
caafcf61aa | ||
|
|
e6280377ce | ||
|
|
a2e66550bf | ||
|
|
abad45f874 | ||
|
|
fa649c1cd0 | ||
|
|
96ad12a1b7 | ||
|
|
669979531c | ||
|
|
0c81f5c36c | ||
|
|
7dfc48744c | ||
|
|
943d95eed0 | ||
|
|
4b6e5e32d1 | ||
|
|
a1132fbe90 | ||
|
|
ea37370738 | ||
|
|
3b73f86e1b | ||
|
|
1633dedee3 | ||
|
|
9ae41c67a2 | ||
|
|
58819f48bc | ||
|
|
1f2cc92533 | ||
|
|
2e550b95ca | ||
|
|
6f27a0db4d | ||
|
|
ace7998c40 | ||
|
|
969d65bbb2 | ||
|
|
dd3c6abdd2 | ||
|
|
45c50c1f6e | ||
|
|
93d284dc82 | ||
|
|
9f6269c3e8 | ||
|
|
54ed9cfe22 | ||
|
|
efe0b7ace0 | ||
|
|
96eeac14ea | ||
|
|
85409f1616 | ||
|
|
f7f020cc26 | ||
|
|
889173a682 | ||
|
|
e2fab353fd | ||
|
|
41a0769cca | ||
|
|
f73fac0eb4 | ||
|
|
3047e40116 | ||
|
|
24c45c7cc2 | ||
|
|
88b2ce84d6 | ||
|
|
9c55543f1b | ||
|
|
76eac28192 | ||
|
|
b3d2c28f06 | ||
|
|
a4c0a56734 | ||
|
|
fc78637d2f | ||
|
|
7605706b89 | ||
|
|
862f3e4e9d | ||
|
|
64de8327a6 | ||
|
|
c8d5624600 | ||
|
|
abe2d7d10a | ||
|
|
27dbddac94 | ||
|
|
cfd18eef5c | ||
|
|
e2b30b8f71 | ||
|
|
0f86978d8a | ||
|
|
1e7c21bdcf | ||
|
|
9e8fa209d5 | ||
|
|
83caef1a97 | ||
|
|
056daed06b | ||
|
|
ab31c8fa9d | ||
|
|
f95fb7d08b | ||
|
|
b2939f511d | ||
|
|
e1a08629a4 | ||
|
|
9c5eba7372 | ||
|
|
97ecf45da3 | ||
|
|
cf4321492e | ||
|
|
d5f996d249 | ||
|
|
dc65f639c7 | ||
|
|
64aa9fe0ae | ||
|
|
73f65505b0 | ||
|
|
49dc938eb3 | ||
|
|
687b547b47 | ||
|
|
4b85ef2a99 | ||
|
|
be7ef159cd | ||
|
|
8b6f0fb8eb | ||
|
|
ef29c61fb0 | ||
|
|
77ba81d621 | ||
|
|
4d645f761c | ||
|
|
b0aeaaabb4 | ||
|
|
80661e1646 | ||
|
|
f53e4495de | ||
|
|
76ee1e776f | ||
|
|
81c880efb1 | ||
|
|
3b3c955e2c | ||
|
|
d786509a06 | ||
|
|
7e682810fb | ||
|
|
446fc7dc35 | ||
|
|
76ccc2f8b1 | ||
|
|
6f99e6b840 | ||
|
|
08d6b11add | ||
|
|
dccf161d54 | ||
|
|
c648a8ccbc | ||
|
|
83dc9b688b | ||
|
|
2d53623ab2 | ||
|
|
d0d14b9006 | ||
|
|
db85958008 | ||
|
|
58866b0595 | ||
|
|
f6f2d5e4de | ||
|
|
118aa4bda1 | ||
|
|
ba9b4617f7 | ||
|
|
a3646e83c8 | ||
|
|
b51b8e9c63 | ||
|
|
3638372602 | ||
|
|
647a0d8a2f | ||
|
|
0bcaf9cb41 | ||
|
|
52c6fb5d06 | ||
|
|
a1a192946c | ||
|
|
713b199f21 | ||
|
|
2364e8dd26 | ||
|
|
60f98c84d5 | ||
|
|
dfea87ed0e | ||
|
|
3010657620 | ||
|
|
35477a5edb | ||
|
|
793bb97909 | ||
|
|
ca4e9c2ea5 | ||
|
|
fa68e989c4 | ||
|
|
47291206ae | ||
|
|
77578174b7 | ||
|
|
f0f7b9e6e4 | ||
|
|
dbb87a6a79 | ||
|
|
8d8c1c505c | ||
|
|
970db2b6d1 | ||
|
|
330e0818fa | ||
|
|
7f4f48aa32 | ||
|
|
fb4300fe4d | ||
|
|
4e89b94916 | ||
|
|
0f76d42b8c | ||
|
|
6cc3060dc2 | ||
|
|
83d629c397 | ||
|
|
b7fe9ba794 | ||
|
|
c0594c5449 | ||
|
|
0eb562adf9 | ||
|
|
181bd88902 | ||
|
|
3d6cfd2457 | ||
|
|
bcb5191ecc | ||
|
|
5581283c52 | ||
|
|
c3cb530aa1 | ||
|
|
5cc0665a9d | ||
|
|
6c0dde8e79 | ||
|
|
a009e06fdf | ||
|
|
4282169fc9 | ||
|
|
7c76e72a7f | ||
|
|
e22e514d29 | ||
|
|
edfbc6ac95 | ||
|
|
a23a1fa928 | ||
|
|
8b2bb48dbc | ||
|
|
930183b0aa | ||
|
|
bfbf1c5e71 | ||
|
|
67684ee0e6 | ||
|
|
336ccd2ec4 | ||
|
|
21f4a79dd5 | ||
|
|
3e94b2854c | ||
|
|
9539060404 | ||
|
|
9f73524dd9 | ||
|
|
321360e180 | ||
|
|
8ac3d0a2a6 | ||
|
|
b80118c6d0 | ||
|
|
d795812fd4 | ||
|
|
aa945e491c | ||
|
|
1e566808cb | ||
|
|
6eaa1e9edd | ||
|
|
a2d53ce217 | ||
|
|
39707005bc | ||
|
|
dc8d24322f | ||
|
|
912ee35721 | ||
|
|
9321eea8f8 | ||
|
|
347503d96f | ||
|
|
fca3aade63 | ||
|
|
a2cd1fa8ef | ||
|
|
d9db3a6edf | ||
|
|
1d749eba48 | ||
|
|
22917b7e0a | ||
|
|
e4ca35cae8 | ||
|
|
0c74ee2602 | ||
|
|
40e533d9d8 | ||
|
|
01f4bb9494 | ||
|
|
e386c8e87c | ||
|
|
bf3b1b03e8 | ||
|
|
a69c1534f6 | ||
|
|
41671d7acf | ||
|
|
a909fc1fa8 | ||
|
|
919a49b22e | ||
|
|
9ae599cf05 | ||
|
|
5aad37c613 | ||
|
|
69e580e37d | ||
|
|
c735195d33 | ||
|
|
857f0d2dcc | ||
|
|
9b6e1f4c64 | ||
|
|
8a41b35317 | ||
|
|
f08cb0f0b5 | ||
|
|
bbf1e373ad | ||
|
|
069403ab6c | ||
|
|
73ac435b92 | ||
|
|
4b439dc7bb | ||
|
|
4c7879f423 | ||
|
|
fc7540ed1e | ||
|
|
08fd9b98c8 | ||
|
|
1af8cee719 | ||
|
|
ca6c9c454d | ||
|
|
8adcc14422 | ||
|
|
65777750ab | ||
|
|
96b437c0b4 | ||
|
|
d3ae5c38d9 | ||
|
|
c6452b2fea | ||
|
|
caf1900484 | ||
|
|
2e79d34f75 | ||
|
|
28566c4272 | ||
|
|
28fd2d18fa | ||
|
|
df7ce97d40 | ||
|
|
5b2504ba3f | ||
|
|
cbf4d17c7f | ||
|
|
642ba05dcf | ||
|
|
b2b5e87aed | ||
|
|
3562f3192f | ||
|
|
e58d8b35b8 | ||
|
|
2761a3778a | ||
|
|
1eaa8d9b67 | ||
|
|
169b292301 | ||
|
|
249cfdb78d | ||
|
|
3f34e17bd4 | ||
|
|
196b48cbd3 | ||
|
|
01bfa59c6a | ||
|
|
a921ef1ec7 | ||
|
|
d30f2b9d16 | ||
|
|
8f887e5242 | ||
|
|
f742a65af8 | ||
|
|
868ba6d232 | ||
|
|
47384bec0e | ||
|
|
2144de87d4 | ||
|
|
7a3c9c5b49 | ||
|
|
df35468771 | ||
|
|
5ac9d3fdff | ||
|
|
de73ed1986 | ||
|
|
da2a92b74a | ||
|
|
9aba2db8ce | ||
|
|
5141be475b | ||
|
|
9f8e7616d1 | ||
|
|
f7c7cdef8c | ||
|
|
98e529304f | ||
|
|
c04290493e | ||
|
|
12695faba2 | ||
|
|
c1a064e40a | ||
|
|
7798594071 | ||
|
|
34281c2850 | ||
|
|
17365a6e2c | ||
|
|
9b150e3263 | ||
|
|
a1648c70c6 | ||
|
|
1a0aa37992 | ||
|
|
ad4772ba01 | ||
|
|
78b6e16eb3 | ||
|
|
682c61d025 | ||
|
|
b026479f78 | ||
|
|
1d6fbba6d2 | ||
|
|
142a47346d | ||
|
|
406d1023e8 | ||
|
|
895db0526b | ||
|
|
d2ecf48af9 | ||
|
|
edca8bec50 | ||
|
|
b99571c28f | ||
|
|
70d9d9273b | ||
|
|
bcd0b0cd46 | ||
|
|
49238a1283 | ||
|
|
7a11129a5e | ||
|
|
395fc13142 | ||
|
|
3b594d239c | ||
|
|
e7701ee0f9 | ||
|
|
80e8f5db8a | ||
|
|
608eea8e7d | ||
|
|
7dae92b042 | ||
|
|
62bc67cbe1 | ||
|
|
fbcbf0afe5 | ||
|
|
6b06873568 | ||
|
|
2007a8d05c | ||
|
|
1007772ef3 | ||
|
|
23785ea8b6 | ||
|
|
10a6b43a97 | ||
|
|
72e1d8177c | ||
|
|
6ab9bfa0fa | ||
|
|
faeb73d8b7 | ||
|
|
beff3d4832 | ||
|
|
29a4fdb47a | ||
|
|
931411b6e4 | ||
|
|
8157172a9f | ||
|
|
5ac73d9f90 | ||
|
|
27085243c4 | ||
|
|
16b2fd93d8 | ||
|
|
edce6ed9e8 | ||
|
|
e9b6db6994 | ||
|
|
f5a1671e86 | ||
|
|
560d8ec5c0 | ||
|
|
4e7a3fe515 | ||
|
|
1ac28b5203 | ||
|
|
9b69d79a78 | ||
|
|
9e78a4ef99 | ||
|
|
8d2ef41d54 | ||
|
|
c507e960fd | ||
|
|
490f700e37 | ||
|
|
355f379f21 | ||
|
|
e69f8a2e7a | ||
|
|
450d5b6144 | ||
|
|
41fdc485b4 | ||
|
|
4d25aee6dc | ||
|
|
46574f8137 | ||
|
|
71cbd1221b | ||
|
|
7386a9b614 | ||
|
|
d5cfc39744 | ||
|
|
8a1770ffda | ||
|
|
01e51ecb73 | ||
|
|
43a62813e6 | ||
|
|
1b25438fca | ||
|
|
5858de4860 | ||
|
|
8128a3374b | ||
|
|
32cc860eda | ||
|
|
03d6687c64 | ||
|
|
51924d16db | ||
|
|
cd167313b7 | ||
|
|
365142c860 | ||
|
|
990f6f148a | ||
|
|
1eb6f33207 | ||
|
|
2d23a5b37d | ||
|
|
65f400cf46 | ||
|
|
84dfdfc11c | ||
|
|
ecb1cb8a0e | ||
|
|
b9a8da1231 | ||
|
|
27c9bee138 | ||
|
|
9902718082 | ||
|
|
270ab5e843 | ||
|
|
62961889d5 | ||
|
|
0217c4b4b2 | ||
|
|
1c950a8a57 | ||
|
|
f5563ccedb | ||
|
|
0672477817 | ||
|
|
c28a1f740e | ||
|
|
e0f3b65251 | ||
|
|
001acda866 | ||
|
|
eb9ceb4dc8 | ||
|
|
d622f9075a | ||
|
|
76699ad323 | ||
|
|
a5fc5c1e40 | ||
|
|
cb9302b2fe | ||
|
|
254ddf8dfb | ||
|
|
4995cc8876 | ||
|
|
1f120a7faf | ||
|
|
4411910930 | ||
|
|
931dc04ad2 | ||
|
|
e6b390addc | ||
|
|
a4027c0ad8 | ||
|
|
3451fbf42a | ||
|
|
65e3c24de6 | ||
|
|
90e74c1c3a | ||
|
|
4610729a82 | ||
|
|
ab8c2c8b10 | ||
|
|
bf0a5e13ae | ||
|
|
e4f8f7852f | ||
|
|
348365b8fd | ||
|
|
5c2f62defa | ||
|
|
bd5a98fe13 | ||
|
|
8a0da67705 | ||
|
|
479cbbf671 | ||
|
|
6708ca00b5 | ||
|
|
53e08bbf55 | ||
|
|
d389050a66 | ||
|
|
5d4f628b8b | ||
|
|
2ecbd26477 | ||
|
|
427e0d895f | ||
|
|
dfb86d40a5 | ||
|
|
b08c7cc9e6 | ||
|
|
2bbe9464b8 | ||
|
|
ef5937d5ee | ||
|
|
289846a53a | ||
|
|
6bc361bd0c | ||
|
|
47274b0085 | ||
|
|
d3ebd83c32 | ||
|
|
b076a57811 | ||
|
|
e9285ad46d | ||
|
|
5d91a1159e | ||
|
|
ee65c22a40 | ||
|
|
1cb5c9a711 | ||
|
|
f22a3e081d | ||
|
|
fff6a529e5 | ||
|
|
2260957b00 | ||
|
|
42c845bb72 | ||
|
|
8b7ae19508 | ||
|
|
c13274d0a7 | ||
|
|
2a424ca3a9 | ||
|
|
8effb2cb76 | ||
|
|
a03ce0e82c | ||
|
|
c0913a1b05 | ||
|
|
f504581468 | ||
|
|
f4528151ac | ||
|
|
2475ea9ec9 | ||
|
|
cc380a99f1 | ||
|
|
7c34720cf7 | ||
|
|
6debbe6853 | ||
|
|
d9cf784498 | ||
|
|
12b8ec740b | ||
|
|
ff6d492857 | ||
|
|
4ea29526b1 | ||
|
|
aae3772a3a | ||
|
|
3639ced11f | ||
|
|
a5dfcd4eb6 | ||
|
|
204191973e | ||
|
|
39b55f9045 | ||
|
|
f02bb9d4ba | ||
|
|
8b897c8a55 | ||
|
|
f25bc35ee3 | ||
|
|
81a41fb941 | ||
|
|
c714ac2175 | ||
|
|
f131bcb0d9 | ||
|
|
07627b9320 | ||
|
|
f32dab48a4 | ||
|
|
61abb8cf0d | ||
|
|
bbbee3421d | ||
|
|
972a79309b | ||
|
|
d318606102 | ||
|
|
8b057ea199 | ||
|
|
edaa9f885a | ||
|
|
616760cbbb | ||
|
|
6a120f5839 | ||
|
|
3fa7546ba6 | ||
|
|
b7fd4d094c | ||
|
|
fb92e06d62 | ||
|
|
744573817a | ||
|
|
b84dc7b721 | ||
|
|
e88191c54a | ||
|
|
a32bc8c378 | ||
|
|
cb5be7cec7 | ||
|
|
209eb9c397 | ||
|
|
58b6b1cd0c | ||
|
|
b8c27ec2a9 | ||
|
|
a17c42e07e | ||
|
|
cb9a2d7b8b | ||
|
|
a7b215fa19 | ||
|
|
367170d33b | ||
|
|
695cb1496d | ||
|
|
741ca2646e | ||
|
|
2ac4b923fe | ||
|
|
cac15b1436 | ||
|
|
e77281cfb6 | ||
|
|
81d0a71060 | ||
|
|
66cb1c28f7 | ||
|
|
051e3eb922 | ||
|
|
bd93dd543e | ||
|
|
1382ef1724 | ||
|
|
6004850792 | ||
|
|
79d43f76b3 | ||
|
|
d387287a04 | ||
|
|
413e209b0b | ||
|
|
1124069e52 | ||
|
|
3d0009f23e | ||
|
|
4df0cf1049 | ||
|
|
eb91e10661 | ||
|
|
88dbfba282 | ||
|
|
f6358f10f2 | ||
|
|
51b5c81aac | ||
|
|
20ff40896f | ||
|
|
df78d375fe | ||
|
|
206194353c | ||
|
|
dcd16704bd | ||
|
|
2b6b2a5e95 | ||
|
|
c767a3b3ad | ||
|
|
06a4eac1c9 | ||
|
|
ffba3ef18a | ||
|
|
1a6d6e1290 | ||
|
|
e75dedcffa | ||
|
|
a6e50452f0 | ||
|
|
008dd2f1d7 | ||
|
|
82c3f32a5e | ||
|
|
5c0a03e242 | ||
|
|
e25ce911ed | ||
|
|
b032002c33 | ||
|
|
667bd138c8 | ||
|
|
f0f017713e | ||
|
|
331ae6ff44 | ||
|
|
ee05f34963 | ||
|
|
2f6aa3dc0f | ||
|
|
c9c5ae0058 | ||
|
|
bff5a13c25 | ||
|
|
68781783c4 | ||
|
|
04ba6bdd7b | ||
|
|
f557420c7a | ||
|
|
8d2d633f6d | ||
|
|
39ed24d133 | ||
|
|
94ec700c78 | ||
|
|
92ac19fa2e | ||
|
|
c96aeb25e5 | ||
|
|
63ebd462b9 | ||
|
|
68fe024d5a | ||
|
|
12c0207f22 | ||
|
|
402eade4c2 | ||
|
|
c3f88773d2 | ||
|
|
5c689ebf9b | ||
|
|
efa99cb9ac | ||
|
|
19a96aac00 | ||
|
|
6c84d433f6 | ||
|
|
63c4ab822c | ||
|
|
f343397a68 | ||
|
|
3993f5a715 | ||
|
|
ac66b6ac1b | ||
|
|
de5c07d74b | ||
|
|
4f8a9b4d3f | ||
|
|
1e643b3a99 | ||
|
|
eddbe33f2e | ||
|
|
6b07e07bea | ||
|
|
8ff8502fed | ||
|
|
31ee93a2a0 | ||
|
|
ce17685154 | ||
|
|
4927e6135d | ||
|
|
6725873c8c | ||
|
|
90b1a244a5 | ||
|
|
c3a7191f31 | ||
|
|
e3a2a98c2f | ||
|
|
35aaafeba6 | ||
|
|
0c9d3c9152 | ||
|
|
6a93608427 | ||
|
|
c2b84ce240 | ||
|
|
e6b876314c | ||
|
|
9cb73fe9fc | ||
|
|
f4f0ae990f | ||
|
|
50c314af7d | ||
|
|
bd18a5bb41 | ||
|
|
263faa9895 | ||
|
|
36cc45b227 | ||
|
|
66ce27593b | ||
|
|
9a62690627 | ||
|
|
26dcee20dc | ||
|
|
8c73de6f94 | ||
|
|
8a3a3d05c4 | ||
|
|
11d78d0b44 | ||
|
|
ae3fb2d41e | ||
|
|
54d66c0119 | ||
|
|
beb37bad9a | ||
|
|
cb25353e60 | ||
|
|
7bd24f52c7 | ||
|
|
bfd646f409 | ||
|
|
ec5ade7b38 | ||
|
|
4bb6a64153 | ||
|
|
e7a29f6626 | ||
|
|
7d168d7f24 | ||
|
|
8b0e10442f | ||
|
|
10ed279650 | ||
|
|
18300fb1a2 | ||
|
|
82543ee67e | ||
|
|
58c8a23279 | ||
|
|
6b7db83a40 | ||
|
|
3d5a7e9bc7 | ||
|
|
d1e8f9db6b | ||
|
|
532e6bee5b | ||
|
|
a7af611dfb | ||
|
|
de1296f750 | ||
|
|
43e1c20046 | ||
|
|
b285205376 | ||
|
|
db4ec5eb07 | ||
|
|
51e6b6c908 | ||
|
|
e57ab4cdb0 | ||
|
|
c4d4a7f2c3 | ||
|
|
66045abf16 | ||
|
|
019af5d238 | ||
|
|
d71d10a97f | ||
|
|
f374466d0a | ||
|
|
97d9c57951 | ||
|
|
b651c1f9e9 | ||
|
|
d448b59dcc | ||
|
|
06ccc2d9c6 | ||
|
|
bf2ee271fd | ||
|
|
b9486e8853 | ||
|
|
7911f9bd20 | ||
|
|
6753039b92 | ||
|
|
802eff0796 | ||
|
|
ff8141705d | ||
|
|
b28f8f50e1 | ||
|
|
49a1f9527c | ||
|
|
240f5e6419 | ||
|
|
2f26cbe676 | ||
|
|
2a304bd413 | ||
|
|
189b0ea122 | ||
|
|
70b24f9b86 | ||
|
|
6c11701032 | ||
|
|
d4a28c0e7b | ||
|
|
0e50de91b4 | ||
|
|
ac78b96c2b | ||
|
|
aca091e3c3 | ||
|
|
572e7f5be8 | ||
|
|
ef4ab9570a | ||
|
|
6207299219 | ||
|
|
8eb8c666b6 | ||
|
|
8f40e8ae04 | ||
|
|
b93131b990 | ||
|
|
6bce621da4 | ||
|
|
9e9bc5193f | ||
|
|
178f937727 | ||
|
|
d94abfaef9 | ||
|
|
c03da7eb74 | ||
|
|
c39313d973 | ||
|
|
a0c126f39e | ||
|
|
354d184664 | ||
|
|
0e49d103b1 | ||
|
|
fbbae218c0 | ||
|
|
12387f0efb | ||
|
|
41be0bb069 | ||
|
|
6edcb95aab | ||
|
|
f022419ceb | ||
|
|
9232042b02 | ||
|
|
1c642a3cb2 | ||
|
|
b5e86473a4 | ||
|
|
8417f5cb7a | ||
|
|
f434be45b6 | ||
|
|
6d493aeb9c | ||
|
|
b4052039e3 | ||
|
|
727689a3bd | ||
|
|
b86f9792f3 | ||
|
|
83f4a5ea73 | ||
|
|
80ddea2c8c | ||
|
|
6618f71297 | ||
|
|
5c1c039188 | ||
|
|
af1afb5348 | ||
|
|
5bbd527837 | ||
|
|
2eb71fe1c9 | ||
|
|
e1157ad01a | ||
|
|
e5df3b9cb3 | ||
|
|
a52f7dc540 | ||
|
|
eff69eba7d | ||
|
|
f8a41b39ec | ||
|
|
cd31806100 | ||
|
|
e5d588c4eb | ||
|
|
3b0483752c | ||
|
|
5ea5ca0ef7 | ||
|
|
08bd2c4724 | ||
|
|
1c1a4d92f4 | ||
|
|
37e5dda38f | ||
|
|
be6232ee82 | ||
|
|
651fa42e4a | ||
|
|
c69d261e56 | ||
|
|
54446949a5 | ||
|
|
161d4ed314 | ||
|
|
a88c08bf3f | ||
|
|
eae4a0be35 | ||
|
|
0e28b40c25 | ||
|
|
f8adbcc252 | ||
|
|
6f9d557a64 | ||
|
|
ac939de42f | ||
|
|
82756e1073 | ||
|
|
1b14148b0f | ||
|
|
0cfb5eff0e | ||
|
|
ce3bab8a33 | ||
|
|
e56b8545c7 | ||
|
|
ac7fc21db5 | ||
|
|
48bc2174a4 | ||
|
|
d11d58889b | ||
|
|
8cf01c318a | ||
|
|
e18007e902 | ||
|
|
1f13b8d43d | ||
|
|
c6b82043d0 | ||
|
|
671937ff7f | ||
|
|
27478966bc | ||
|
|
442ea94cde | ||
|
|
13bd24ef83 | ||
|
|
32240b6004 | ||
|
|
abd0880c66 | ||
|
|
bb6c5f8e44 | ||
|
|
66d0141fae | ||
|
|
57cf7a582b | ||
|
|
706827fc0c | ||
|
|
54009dcb06 | ||
|
|
1049deb299 | ||
|
|
19b928782a | ||
|
|
4aafafb5bd | ||
|
|
65ff5a8c51 | ||
|
|
d2449c181a | ||
|
|
9527a33a58 | ||
|
|
419a513d8f | ||
|
|
a1e7ecbecc | ||
|
|
f996c90653 | ||
|
|
be2a7f5a51 | ||
|
|
a6ca193ae0 | ||
|
|
ff7f81c307 | ||
|
|
cad42948cd | ||
|
|
5ad88099e1 | ||
|
|
c849c2b867 | ||
|
|
1571263575 | ||
|
|
a335d60fcb | ||
|
|
c78c182e32 | ||
|
|
8ff5d05078 | ||
|
|
80a1c13a07 | ||
|
|
0b6ed46a0a | ||
|
|
cdaba3483a | ||
|
|
3681dba5fb | ||
|
|
ddbe5af21c | ||
|
|
da4263131e | ||
|
|
f3b771cb7d | ||
|
|
39c764e283 | ||
|
|
9d4e02c2d4 | ||
|
|
34ab157046 | ||
|
|
e76403f54f | ||
|
|
3f7b1ce204 | ||
|
|
0eff1225b8 | ||
|
|
4e2d024b2a | ||
|
|
1db289c35e | ||
|
|
c8a4a41eaf | ||
|
|
6221f87b8c | ||
|
|
ab3b682c47 | ||
|
|
38d6ab9895 | ||
|
|
4d6da7454c | ||
|
|
19ac30eed0 | ||
|
|
84a7b79e3c | ||
|
|
e2fc8015b8 | ||
|
|
5aac30f44a | ||
|
|
d9340349fa | ||
|
|
82019b5f40 | ||
|
|
06e0a82927 | ||
|
|
53409a6c70 | ||
|
|
59b68d80ca | ||
|
|
4f38f5a389 | ||
|
|
7a0bb07fc1 | ||
|
|
b1d3e712be | ||
|
|
bc0cd4a783 | ||
|
|
d4dc65c45f | ||
|
|
dd40a1de6b | ||
|
|
ea65a3423e | ||
|
|
04811da9cc | ||
|
|
46a4243093 | ||
|
|
03bee3e11e | ||
|
|
20491e1136 | ||
|
|
4ea01ea135 | ||
|
|
8440f72e9e | ||
|
|
65291259ee | ||
|
|
d42ee05740 | ||
|
|
c4bf644240 | ||
|
|
fe95ad2e11 | ||
|
|
644f80df57 | ||
|
|
46e5c3233a | ||
|
|
1da27edde6 | ||
|
|
75da8833dc | ||
|
|
c40a132811 | ||
|
|
288267ae11 | ||
|
|
8f1a493327 | ||
|
|
f97adc325f | ||
|
|
8aa72e9397 | ||
|
|
3ab7886eab | ||
|
|
551968a38c | ||
|
|
e954102f81 | ||
|
|
5a1d5c0a2d | ||
|
|
42b553266a | ||
|
|
d98d2915ca | ||
|
|
634dced5aa | ||
|
|
5ee94199bb | ||
|
|
23422220ce | ||
|
|
c141462432 | ||
|
|
41abf876a2 | ||
|
|
28793abc7a | ||
|
|
1ce213004a | ||
|
|
e223fa2002 | ||
|
|
6fc542ff7d | ||
|
|
a1dd4ab0e9 | ||
|
|
1232f1be28 | ||
|
|
738c7b968a | ||
|
|
d6e7d1ffee | ||
|
|
6fcdee14e4 | ||
|
|
8c139ad044 | ||
|
|
122baba999 | ||
|
|
27b12f5263 | ||
|
|
a801424b35 | ||
|
|
717a3eaede | ||
|
|
0061f35b46 | ||
|
|
ff20646a63 | ||
|
|
a5f4375ff2 | ||
|
|
99b2cbb606 | ||
|
|
15e8eee373 | ||
|
|
4cd2bc266a | ||
|
|
92472c815a | ||
|
|
a246257eff | ||
|
|
b6bab552be | ||
|
|
1afce00069 | ||
|
|
9ddfdf3638 | ||
|
|
06230ffa40 | ||
|
|
cac7ac2a8a | ||
|
|
fbbe4f5579 | ||
|
|
7d8373643f | ||
|
|
97bf88421e | ||
|
|
c067ab7cc9 | ||
|
|
b21e4f5296 | ||
|
|
ea0add13ad | ||
|
|
bfc3157186 | ||
|
|
a5aeb43305 | ||
|
|
2b77a15845 | ||
|
|
0d1b751600 | ||
|
|
754e2a7aa7 | ||
|
|
731f375332 | ||
|
|
faa8013208 | ||
|
|
90afe81856 | ||
|
|
c13e379908 | ||
|
|
689d3bb9b5 | ||
|
|
9ae8fcf3bc | ||
|
|
74f3a0a5ed | ||
|
|
d51e20bffe | ||
|
|
c5d6214ab2 | ||
|
|
898921ea4f | ||
|
|
8c6194da35 | ||
|
|
8b2a3ccb42 | ||
|
|
820c869e2c | ||
|
|
9a21a3e2a4 | ||
|
|
03e47cee56 | ||
|
|
1a28f15f75 | ||
|
|
72d5c74631 | ||
|
|
322ebdf8a6 | ||
|
|
3d3296d1b7 | ||
|
|
e3715c3f81 | ||
|
|
db9e019519 | ||
|
|
3a2ea56c92 | ||
|
|
df6b552ac1 | ||
|
|
9bb4d7ed93 | ||
|
|
562ef1b147 | ||
|
|
6d31a2f656 | ||
|
|
62c16d9f08 | ||
|
|
db17973ea6 | ||
|
|
8155376c45 | ||
|
|
ae26c7bc7a | ||
|
|
fa4e4cb2bb | ||
|
|
7842b7e0fd | ||
|
|
52d6245b4a | ||
|
|
4c03d4f0b4 | ||
|
|
de8aaf35a3 | ||
|
|
dda5d8206f | ||
|
|
f8ca2a50b7 | ||
|
|
329f4072be | ||
|
|
a88b392a43 | ||
|
|
28ec418c76 | ||
|
|
ce56b87b16 | ||
|
|
6d3155dff9 | ||
|
|
9fee1fda1e | ||
|
|
1e1c5fa139 | ||
|
|
70fb1090ca | ||
|
|
6f13aa4724 | ||
|
|
584f923c4c | ||
|
|
3d1c39da30 | ||
|
|
a3148c9825 | ||
|
|
40e33a43a9 | ||
|
|
a3fc74c44e | ||
|
|
3b28b5fa1c | ||
|
|
6070dc9db9 | ||
|
|
7608f02c21 | ||
|
|
cec5ec0a78 | ||
|
|
c72c683915 | ||
|
|
3384c773da | ||
|
|
8dfc16880e | ||
|
|
5d65861c49 | ||
|
|
179b1419ab | ||
|
|
4bebc8486c | ||
|
|
fd0f6e4113 | ||
|
|
8e4c40ba2a | ||
|
|
abf1a92977 | ||
|
|
e9ff6ece35 | ||
|
|
ba02ce9261 | ||
|
|
0ece4427b6 | ||
|
|
9afc192154 | ||
|
|
27691d8563 | ||
|
|
0ebc6dd2d8 | ||
|
|
5a2f57172f | ||
|
|
47d30c81b6 | ||
|
|
b80f78d12e | ||
|
|
835ffaafd4 | ||
|
|
66f0d580b9 | ||
|
|
524998811c | ||
|
|
1b3a8d0cf5 | ||
|
|
0134d343b2 | ||
|
|
202ddd8157 | ||
|
|
76159b2cc4 | ||
|
|
620e95cebf | ||
|
|
5f5357d28a | ||
|
|
faee08bb90 | ||
|
|
178f98c82f | ||
|
|
d0dc16aa6f | ||
|
|
77e56e8da9 | ||
|
|
045d7bb5ae | ||
|
|
135f043b60 | ||
|
|
682884355f | ||
|
|
702b473453 | ||
|
|
3f803a3f48 | ||
|
|
742be996fe | ||
|
|
228d929a5c | ||
|
|
703fddeb65 | ||
|
|
be89397e48 | ||
|
|
2a71716ba4 | ||
|
|
b8b5561850 | ||
|
|
88694f4256 | ||
|
|
4d7126d3c3 | ||
|
|
87ab6c1de2 | ||
|
|
e649c9128e | ||
|
|
9b774734c3 | ||
|
|
841164bafb | ||
|
|
bc61ec0495 | ||
|
|
c37bc213e5 | ||
|
|
080639d2d9 | ||
|
|
ab2d0771ab | ||
|
|
204d502c24 | ||
|
|
985b60a565 | ||
|
|
8202c4abe3 | ||
|
|
4de73cebe0 | ||
|
|
9bb6e833fb | ||
|
|
d227da0c10 | ||
|
|
962330d491 | ||
|
|
c5d9a98549 | ||
|
|
9b83c78346 | ||
|
|
8a7d0d4242 | ||
|
|
45440adc01 | ||
|
|
8feaf9ed3e | ||
|
|
636020d870 | ||
|
|
3150aa2e7c | ||
|
|
ce22449f41 | ||
|
|
1c938b5d29 | ||
|
|
0493104beb | ||
|
|
9786d9f927 | ||
|
|
73e5254e2b | ||
|
|
7c934d29ac | ||
|
|
bccbf3e55e | ||
|
|
8196ee531a | ||
|
|
39729cb3da | ||
|
|
cdc1f6014e | ||
|
|
aadfdac4c5 | ||
|
|
2e358116c2 | ||
|
|
71d88523b2 | ||
|
|
65e702aed7 | ||
|
|
99bb45d96a | ||
|
|
4f41be641a | ||
|
|
5ab030242b | ||
|
|
736d9bbd98 | ||
|
|
6295ebdb09 | ||
|
|
380a27b83d | ||
|
|
691af037a4 | ||
|
|
70cb78a67d | ||
|
|
4bd5fe0178 | ||
|
|
babd80edd9 | ||
|
|
74e4efba52 | ||
|
|
2d7e88a8c2 | ||
|
|
d82c8d466a | ||
|
|
9edbbf399f | ||
|
|
345316b9b0 | ||
|
|
0bb6636613 | ||
|
|
1929b75e34 | ||
|
|
245827825e | ||
|
|
fc9f81c5c9 | ||
|
|
987dce2643 | ||
|
|
4f2abad39d | ||
|
|
354000082b | ||
|
|
b8e87604c2 | ||
|
|
49f7ad1281 | ||
|
|
6731cdcc9d | ||
|
|
5306510ec1 | ||
|
|
e8c0655186 | ||
|
|
18d0bf1c7b | ||
|
|
0b9755405e | ||
|
|
8b7a183e63 | ||
|
|
7a094478f3 | ||
|
|
bd063c71f8 | ||
|
|
693ef6c4d3 | ||
|
|
60a02cd2a5 | ||
|
|
e25c7dac4a | ||
|
|
d918cade67 | ||
|
|
982400b74d | ||
|
|
c00a384591 | ||
|
|
b0e1630fc5 | ||
|
|
50ffd7b4a9 | ||
|
|
e4e75a5f73 | ||
|
|
7da4381d38 | ||
|
|
ef8076b626 | ||
|
|
ab5f5c1ce9 | ||
|
|
6d442b12a0 | ||
|
|
0019f83e33 | ||
|
|
f3011b1790 | ||
|
|
ea79f84b71 | ||
|
|
040d6929dc | ||
|
|
2fe9c38fff | ||
|
|
256b54b6b5 | ||
|
|
062ad5d92f | ||
|
|
5806ef248f | ||
|
|
257fcf1b3d | ||
|
|
284db016a0 | ||
|
|
5e2898f0ae | ||
|
|
207408ae20 | ||
|
|
2843ca9b49 | ||
|
|
efc09d8a3f | ||
|
|
e6714bf45e | ||
|
|
8049e2c322 | ||
|
|
fdb0db2d41 | ||
|
|
7465031d44 | ||
|
|
15eb3a421d | ||
|
|
ec52a07fe9 | ||
|
|
b7a475cc0a | ||
|
|
aed022d099 | ||
|
|
86b37e7df0 | ||
|
|
09e9b46efd | ||
|
|
90b685c15d | ||
|
|
daedffc0da | ||
|
|
c489f88f77 | ||
|
|
33779bb6cf | ||
|
|
660806fb7c | ||
|
|
40a52ed00b | ||
|
|
c216498198 | ||
|
|
547c887c0b | ||
|
|
dfe23da44d | ||
|
|
a5e8b88e32 | ||
|
|
539c94f36f | ||
|
|
fce5ae0498 | ||
|
|
8fbf96098e | ||
|
|
c8b43fa8ab | ||
|
|
9f305347a8 | ||
|
|
94d826d2a4 | ||
|
|
118cf70e40 | ||
|
|
895d8d594b | ||
|
|
60ce467859 | ||
|
|
6efa58006e | ||
|
|
f253019daf | ||
|
|
8600aceb64 | ||
|
|
a358af534a | ||
|
|
d080676997 | ||
|
|
b3412fcfb4 | ||
|
|
b5e3db279d | ||
|
|
a29ca46ca3 | ||
|
|
2fe54a28b1 | ||
|
|
6f9bc11078 | ||
|
|
cd5186cd0d | ||
|
|
34cdc299b7 | ||
|
|
274a3f78f2 | ||
|
|
0e31ad96a5 | ||
|
|
b191e4d83a | ||
|
|
16c554aa43 | ||
|
|
80550cd42e | ||
|
|
3ac28aa279 | ||
|
|
f16ea99cbc | ||
|
|
d4353bd237 | ||
|
|
8efa82abc2 | ||
|
|
a5f42f77ad | ||
|
|
384314d9ab | ||
|
|
56d63a46bf | ||
|
|
da00ccff58 | ||
|
|
313d5ab762 | ||
|
|
a569029c40 | ||
|
|
04748cf03f | ||
|
|
91871fb96d | ||
|
|
7292a12ff4 | ||
|
|
9b04e8ff38 | ||
|
|
b85a2ea6d2 | ||
|
|
da6c134540 | ||
|
|
e09da1a600 | ||
|
|
5aa1f9863d | ||
|
|
1eeb3637e8 | ||
|
|
79e229467e | ||
|
|
d483ea7f47 | ||
|
|
4e4b31a0d5 | ||
|
|
eb38c1ec15 | ||
|
|
b33406da4d | ||
|
|
10f4da71b8 | ||
|
|
6d27a8e489 | ||
|
|
c63477c34e | ||
|
|
761d1877fa | ||
|
|
cf4dbf6ed9 | ||
|
|
53e2df8aa2 | ||
|
|
07278de968 | ||
|
|
6710c47db0 | ||
|
|
a5cc820ea6 | ||
|
|
5eb95980bf | ||
|
|
57cc0e920a | ||
|
|
902b707b5e | ||
|
|
98600e9a19 | ||
|
|
fbf8ac4883 | ||
|
|
4c7a25407a | ||
|
|
af9359850d | ||
|
|
abe5638af5 | ||
|
|
c45e8a11d7 | ||
|
|
62ef84ea07 | ||
|
|
17b5098602 | ||
|
|
272a7afe9e | ||
|
|
9c7fa1a6a8 | ||
|
|
38a050dd13 | ||
|
|
377396d77c | ||
|
|
80fc8eddb9 | ||
|
|
d9c23ece15 | ||
|
|
84a913451f | ||
|
|
59794efc95 | ||
|
|
13977a07e5 | ||
|
|
b1eb8bd8ec | ||
|
|
69043cfd1c | ||
|
|
f34c713e7e | ||
|
|
e180bf21a7 | ||
|
|
fdaaa4be30 | ||
|
|
4db445dd3e | ||
|
|
530d4c0b08 | ||
|
|
100ce6ded9 | ||
|
|
03deb78aab | ||
|
|
bb220366be | ||
|
|
cd4e4a0892 | ||
|
|
aa42c302aa | ||
|
|
5128557e4e | ||
|
|
a181f5d1db | ||
|
|
5e3bc6c507 | ||
|
|
b11a9557d7 | ||
|
|
593e172832 | ||
|
|
6b62a7fde3 | ||
|
|
9a80028ec7 | ||
|
|
1252e22743 | ||
|
|
a3a25d9bef | ||
|
|
08fa3a5768 | ||
|
|
52cd521902 | ||
|
|
6dd5f1991f | ||
|
|
d3d29e03be | ||
|
|
2ddc88dd87 | ||
|
|
f768aa360f | ||
|
|
5ba12d009b | ||
|
|
6259891312 | ||
|
|
fad59e4a0f | ||
|
|
81852fe8c6 | ||
|
|
b33e262de6 | ||
|
|
7963919b72 | ||
|
|
e7f87478b4 | ||
|
|
c6b8d95747 | ||
|
|
8468c5ac52 | ||
|
|
27d7ae4b78 | ||
|
|
fffd2afe5e | ||
|
|
15ba670d5a | ||
|
|
7e7279a642 | ||
|
|
1b765e2408 | ||
|
|
db0ae9253e | ||
|
|
5573218794 | ||
|
|
6e38f16c83 | ||
|
|
4f0d2370ac | ||
|
|
f49f677107 | ||
|
|
cbb11ea723 | ||
|
|
566d011e2e | ||
|
|
1d2a62d06c | ||
|
|
743163fce7 | ||
|
|
afe5333c88 | ||
|
|
3bf15b5a35 | ||
|
|
1c9cfbc4bb | ||
|
|
65093b0388 | ||
|
|
e2320ea77c | ||
|
|
59f414f707 | ||
|
|
2a2c12cf77 | ||
|
|
5fd34dc967 | ||
|
|
65df6765da | ||
|
|
ff9729d27a | ||
|
|
9876741c50 | ||
|
|
a1f69f0732 | ||
|
|
8174b883a0 | ||
|
|
337065271e | ||
|
|
01dd0e7fc4 | ||
|
|
f3ee08ab9a | ||
|
|
ba08c09607 | ||
|
|
aecfd12911 | ||
|
|
c081e48467 | ||
|
|
237b0324c1 | ||
|
|
5aaf39f40b | ||
|
|
7a4ed29945 | ||
|
|
84014ce9b5 | ||
|
|
08c8e84d72 | ||
|
|
43171879a0 | ||
|
|
2f7a30f920 | ||
|
|
27a93b318d | ||
|
|
816bb2008a | ||
|
|
8eb430dffd | ||
|
|
641b5af8eb | ||
|
|
38ef1e4ed9 | ||
|
|
123c41b4e3 | ||
|
|
7001f17c88 | ||
|
|
1f10191bc7 | ||
|
|
ed5f5d3c6b | ||
|
|
a03bb7a353 | ||
|
|
44d4eae8a1 | ||
|
|
22ffcf5660 | ||
|
|
f40ea6cc8d | ||
|
|
adfcb2b4bf | ||
|
|
8d42be3bac | ||
|
|
e2036a5203 | ||
|
|
7107f05adb | ||
|
|
529fc42b72 | ||
|
|
7808605a8c | ||
|
|
0930013cc2 | ||
|
|
51054b70d6 | ||
|
|
471fb621e5 | ||
|
|
1e0c3e7621 | ||
|
|
a75d312770 | ||
|
|
0fd2ec58be | ||
|
|
be484a8077 | ||
|
|
3d8388ad92 | ||
|
|
7a5eac3621 | ||
|
|
ce34e937d6 | ||
|
|
c8646208f0 | ||
|
|
3ed83e4f59 | ||
|
|
ebe6a12555 | ||
|
|
1727456487 | ||
|
|
ef895777ca | ||
|
|
33fa10e9c1 | ||
|
|
1f9094caec | ||
|
|
9a5c58aae8 | ||
|
|
1b1e6c394d | ||
|
|
504872ac1e | ||
|
|
edc40f4a6c | ||
|
|
7b6e08f791 | ||
|
|
b184db2ee6 | ||
|
|
d10bf7fa35 | ||
|
|
ce20d21f4c | ||
|
|
48a35746a0 | ||
|
|
bf9afd4ba1 | ||
|
|
0eb2d59ecc | ||
|
|
b9bcfef817 | ||
|
|
27d01500fa | ||
|
|
d2ea3e4986 | ||
|
|
b0cecebe34 | ||
|
|
f4971589c6 | ||
|
|
506d64dc8b | ||
|
|
f56aa6cc29 | ||
|
|
a17ec8c2db | ||
|
|
a718986e2d | ||
|
|
242e0e7fdc | ||
|
|
75f7cb6df7 | ||
|
|
cf7d0792a2 | ||
|
|
29d260db9d | ||
|
|
d7dfad1fe8 | ||
|
|
82f404e627 | ||
|
|
c49b4c45c8 | ||
|
|
39f3feb210 | ||
|
|
4ed89e9515 | ||
|
|
0b6989c60c | ||
|
|
a9139c6023 | ||
|
|
2de580236e | ||
|
|
e5d4979f3e | ||
|
|
8f00de273a | ||
|
|
1a0f5a7460 | ||
|
|
b0229fb528 | ||
|
|
454f1aa00a | ||
|
|
12c25d786f | ||
|
|
7c65eca1fb | ||
|
|
6a87988d4c | ||
|
|
b65a17c867 | ||
|
|
f067fc9896 | ||
|
|
36cc53b4f8 | ||
|
|
497df9db96 | ||
|
|
26f66b0377 | ||
|
|
69c448f4e6 | ||
|
|
df08191857 | ||
|
|
bb88ff7649 | ||
|
|
7329bf8eef | ||
|
|
a58e409665 | ||
|
|
511a55e22c | ||
|
|
cfa2cfeded | ||
|
|
9d30414539 | ||
|
|
1b34de81eb | ||
|
|
827824500b | ||
|
|
35198ddbe8 | ||
|
|
546545a800 | ||
|
|
1023cc5cb2 | ||
|
|
094cf9e400 | ||
|
|
1bc834efe8 | ||
|
|
b4b039b2b0 | ||
|
|
d8e850673b | ||
|
|
35d7046d69 | ||
|
|
fad87abfea | ||
|
|
10d10ff18d | ||
|
|
161b99f252 | ||
|
|
87ff260c9f | ||
|
|
cc4dafa400 | ||
|
|
9d29904ec5 | ||
|
|
50be4f4ef8 | ||
|
|
5f9c05080f | ||
|
|
bbfcc87a68 | ||
|
|
d7e9510353 | ||
|
|
1a1057f56f | ||
|
|
93ff8cfe1b | ||
|
|
4d05e085ef | ||
|
|
d2d96d2ece | ||
|
|
78762cd479 | ||
|
|
cdaa37c5fd | ||
|
|
76140fb555 | ||
|
|
352c5ea3d3 | ||
|
|
f167d4fa3b | ||
|
|
7528cd3b58 | ||
|
|
cab8d0fb97 | ||
|
|
c7d9c174fd | ||
|
|
b7787c8cf6 | ||
|
|
4d218e3624 | ||
|
|
89fab45eb7 | ||
|
|
9bca338cc2 | ||
|
|
36c3c43f86 | ||
|
|
d9f762c485 | ||
|
|
3d206d1fb1 | ||
|
|
2a959f23d9 | ||
|
|
bb659d39db | ||
|
|
49cdd4d36d | ||
|
|
41d55cf780 | ||
|
|
5c5c948193 | ||
|
|
0e32dc982e | ||
|
|
9fffdb8cad | ||
|
|
76a1da2d0f | ||
|
|
ba012a4b0e | ||
|
|
09fd212e29 | ||
|
|
54fd4787fd | ||
|
|
023499a6c0 | ||
|
|
a8ad7f540b | ||
|
|
b7ee2c696b | ||
|
|
4528223dee | ||
|
|
cf729db0c0 | ||
|
|
a4deafa238 | ||
|
|
5e88eab617 | ||
|
|
950e51e0fe | ||
|
|
1c1d743638 | ||
|
|
ffa7e39121 | ||
|
|
84d6a24446 | ||
|
|
91c92d4b64 | ||
|
|
056229e8ee | ||
|
|
c4c5f7ab6b | ||
|
|
c7ab33f3ab | ||
|
|
3a3850ec4e | ||
|
|
84ed014909 | ||
|
|
39563682f5 | ||
|
|
53179f6e3f | ||
|
|
b1d5ba6787 | ||
|
|
361a3d0371 | ||
|
|
6b5b38535e | ||
|
|
8a4539b19a | ||
|
|
f6ee232d9e | ||
|
|
2562d28f4f | ||
|
|
f89cfa3ec8 | ||
|
|
d8b665f64f | ||
|
|
d473737ca8 | ||
|
|
52e7cc41e2 | ||
|
|
53978333fb | ||
|
|
183209baa3 | ||
|
|
0df4e1147c | ||
|
|
7c42b1f845 | ||
|
|
129e4762dc | ||
|
|
fc1c5ca6e9 | ||
|
|
8ae916ac28 | ||
|
|
0ccf9c961b | ||
|
|
b894e65618 | ||
|
|
801d31ed29 | ||
|
|
34fb9bb865 | ||
|
|
b5201e17ff | ||
|
|
204bbfc6c7 | ||
|
|
692fe0c885 | ||
|
|
3a372179d6 | ||
|
|
3de8bd49ad | ||
|
|
b9c02d5d99 | ||
|
|
185c4c09c9 | ||
|
|
ad709fd8f0 | ||
|
|
da7a1f34e1 | ||
|
|
47d2f3b6ad | ||
|
|
673759f17b | ||
|
|
0e2d6ac196 | ||
|
|
eb99915912 | ||
|
|
501df02260 | ||
|
|
d74f6dba0a | ||
|
|
b590c6b55a | ||
|
|
175f38f114 | ||
|
|
44f69b2b0b | ||
|
|
02a1b139bf | ||
|
|
326cf81a0f | ||
|
|
8952ddc9dc | ||
|
|
dfe2c6d064 | ||
|
|
fea2f30a1c | ||
|
|
5d8bd256f1 | ||
|
|
d7f41f4a56 | ||
|
|
9169ea6ebf | ||
|
|
20314c8952 | ||
|
|
f262075d2a | ||
|
|
37d9ea103b | ||
|
|
ef51f23308 | ||
|
|
11f75176b8 | ||
|
|
bd676c7c5c | ||
|
|
ccf51356a9 | ||
|
|
ece32cbe70 | ||
|
|
574e74350c | ||
|
|
4665f8c6c6 | ||
|
|
a1bdd1fc47 | ||
|
|
cdcb801b50 | ||
|
|
5f4bfed2b5 | ||
|
|
cffaa6fef8 | ||
|
|
bf2e5392f8 | ||
|
|
de6c93b849 | ||
|
|
3b6bbe4799 | ||
|
|
62250cd41f | ||
|
|
69a3963de0 | ||
|
|
acefebfcd7 | ||
|
|
b8f7d084c7 | ||
|
|
fd420b3d81 | ||
|
|
bf799869ef | ||
|
|
8fc3004349 | ||
|
|
13b22e2047 | ||
|
|
2a33ba9f93 | ||
|
|
ad09233d4c | ||
|
|
2be3a38bb0 | ||
|
|
26467221b3 | ||
|
|
dd19ad356a | ||
|
|
4fe91e9a6a | ||
|
|
63d660d152 | ||
|
|
730ff1758f | ||
|
|
174099dd1d | ||
|
|
f7f49b98ff | ||
|
|
18f0af1803 | ||
|
|
af2a4e1ce4 | ||
|
|
a4a58d9a06 | ||
|
|
861bb5f608 | ||
|
|
36c7f1244d | ||
|
|
8e4d07163d | ||
|
|
1af3cd40c0 | ||
|
|
8e0eb9d110 | ||
|
|
bf7dbe0098 | ||
|
|
f37d27f803 | ||
|
|
b98d2fbaa4 | ||
|
|
181539a424 | ||
|
|
6fe299d7c8 | ||
|
|
585eb23b3c | ||
|
|
04940fc4db | ||
|
|
75fa1b7fbe | ||
|
|
cc8e8b2dd1 | ||
|
|
0b3bbad98f | ||
|
|
9da11e05ee | ||
|
|
ecca6f8eda | ||
|
|
b5c6f32d1f | ||
|
|
f38c083b05 | ||
|
|
598a9e0918 | ||
|
|
c93c582ed5 | ||
|
|
010cfb6b35 | ||
|
|
b1d95a5fa0 | ||
|
|
0d4bf2ea2e | ||
|
|
d4eb451ba6 | ||
|
|
9435701d10 | ||
|
|
7603943c38 | ||
|
|
a959f79bcf | ||
|
|
9899d72dce | ||
|
|
4263ea6881 | ||
|
|
ab287858b3 | ||
|
|
eb6d168d50 | ||
|
|
52b8c4ae5e | ||
|
|
1c4640d69b | ||
|
|
3827459229 | ||
|
|
aaeb9f9e84 | ||
|
|
61acb3e42b | ||
|
|
4a8f299bb3 | ||
|
|
5530ca94f3 | ||
|
|
61a1b888bd | ||
|
|
a583701319 | ||
|
|
57623c710a | ||
|
|
93488528de | ||
|
|
e7a5ffdf5f | ||
|
|
dc56cb382b | ||
|
|
bb22ea3c8f | ||
|
|
b856ad0374 | ||
|
|
3aff3c51a8 | ||
|
|
3e096a25f7 | ||
|
|
adb21e4a7e | ||
|
|
3ea268aea7 | ||
|
|
567a8913e4 | ||
|
|
fd89b30192 | ||
|
|
ce3b327cff | ||
|
|
4e5ac4a921 | ||
|
|
b44c1478b8 | ||
|
|
b0890ad4bf | ||
|
|
83d632e24c | ||
|
|
1e5874aed5 | ||
|
|
e807d9a3e7 | ||
|
|
17bb74ff25 | ||
|
|
5c9e2839eb | ||
|
|
d670a761d9 | ||
|
|
b6a3f765a7 | ||
|
|
2ca2ceea5a | ||
|
|
01b4ab512c | ||
|
|
d18a6c3479 | ||
|
|
ab931cdcb4 | ||
|
|
7252b0e69a | ||
|
|
ab0ed23c83 | ||
|
|
18c002094f | ||
|
|
d5f73cb633 | ||
|
|
c738da065e | ||
|
|
a9296d67f7 | ||
|
|
78cb417cfc | ||
|
|
4bec5f6792 | ||
|
|
3da03f66e0 | ||
|
|
e379c676d0 | ||
|
|
6565494781 | ||
|
|
aafcd25403 | ||
|
|
72c4c0007f | ||
|
|
fd1ce7a750 | ||
|
|
fbbdec5e4e | ||
|
|
443b613513 | ||
|
|
2fa44fc0f4 | ||
|
|
5ed21c3ce3 | ||
|
|
f1d52fb530 | ||
|
|
d418ad8369 | ||
|
|
7b58d8e77e | ||
|
|
38971c2946 | ||
|
|
6cef7fd6b6 | ||
|
|
92ffb0b1e5 | ||
|
|
75f03f1fd1 | ||
|
|
0306817ac8 | ||
|
|
03b5dc046d | ||
|
|
bb5cb8a773 | ||
|
|
719bd257a6 | ||
|
|
e21257ab71 | ||
|
|
fc933ba9d5 | ||
|
|
9f9215cb3f | ||
|
|
8308c21d39 | ||
|
|
1f6ef5f045 | ||
|
|
7673918178 | ||
|
|
92b0aad937 | ||
|
|
233e126cd3 | ||
|
|
e9506787d5 | ||
|
|
e061289a26 | ||
|
|
7f87164235 | ||
|
|
0627d37f69 | ||
|
|
68dc49cb88 | ||
|
|
24cf97ca84 | ||
|
|
9859cbdca6 | ||
|
|
b819f303bb | ||
|
|
5ad4a25c33 | ||
|
|
265e5bb122 | ||
|
|
7964f862c6 | ||
|
|
622176d3de | ||
|
|
7e793a48e7 | ||
|
|
5bdc4615e8 | ||
|
|
ef04a4376a | ||
|
|
3c57007765 | ||
|
|
9965f2e79c | ||
|
|
f131997d6a | ||
|
|
08b9e3c46a | ||
|
|
8afbaefa6d | ||
|
|
88a8336ae7 | ||
|
|
0c6dface18 | ||
|
|
9bb2dd3887 | ||
|
|
1f3943fe9a | ||
|
|
9502034d58 | ||
|
|
c6b0b2021c | ||
|
|
0d889ec773 | ||
|
|
3d8f848936 | ||
|
|
6cbd108bca | ||
|
|
03c950f82e | ||
|
|
552b061357 | ||
|
|
ca98432e20 | ||
|
|
6ada1f04da | ||
|
|
edf1738e3c | ||
|
|
5b77205b77 | ||
|
|
67128c2fd5 | ||
|
|
490ec7ef3e | ||
|
|
0ab7ff919b | ||
|
|
489ccd9003 | ||
|
|
9971eef9b8 | ||
|
|
88e8bed395 | ||
|
|
9304942fd6 | ||
|
|
5f3b5cbe26 | ||
|
|
c5dbc8ddca | ||
|
|
eaea041e73 | ||
|
|
20b4ae230f | ||
|
|
037dce8be8 | ||
|
|
3ad6045377 | ||
|
|
3f683c3909 | ||
|
|
2f9974f6ef | ||
|
|
d2156a1179 | ||
|
|
d6a0b92f1e | ||
|
|
15aab58415 | ||
|
|
1e8679bf94 | ||
|
|
4eeb4c515f | ||
|
|
76a75942a9 | ||
|
|
4383c3486a | ||
|
|
e629877984 | ||
|
|
fb71ea83f0 | ||
|
|
d9cf090060 | ||
|
|
a0fd16513a | ||
|
|
c7c6d4e162 | ||
|
|
9a2eaa7a1d | ||
|
|
3b12843d5f | ||
|
|
6685649e39 | ||
|
|
7e63c902d3 | ||
|
|
c1e1530cd9 | ||
|
|
b1a190093c | ||
|
|
fcc7cf0566 | ||
|
|
ca3d51157f | ||
|
|
899a7b2500 | ||
|
|
d19be1731e | ||
|
|
90768b6933 | ||
|
|
89d6f050d1 | ||
|
|
5bfe7a2146 | ||
|
|
e54dc157ac | ||
|
|
5be7472dd9 | ||
|
|
1a2de2bbc2 | ||
|
|
0c9935b1b5 | ||
|
|
aba00ad17b | ||
|
|
5ca05394d6 | ||
|
|
5165415fb1 | ||
|
|
1f1060b97c | ||
|
|
3e9dabe2a5 | ||
|
|
86d72b15c5 | ||
|
|
3fd3a3e5aa | ||
|
|
3fbcf09326 | ||
|
|
d97cd9eabe | ||
|
|
de52bb1036 | ||
|
|
f9a073fd93 | ||
|
|
c77f99cf9b | ||
|
|
af8c1b2092 | ||
|
|
9095dcd9d8 | ||
|
|
08359103da | ||
|
|
9fe15fd8c2 | ||
|
|
728afbc887 | ||
|
|
1e10d49d0f | ||
|
|
4a81ea0ee4 | ||
|
|
acf89b1e9e | ||
|
|
1c99b5bbc7 | ||
|
|
d8d0c67462 | ||
|
|
2e9f050707 | ||
|
|
4b0a3741de | ||
|
|
c45c5f6e92 | ||
|
|
482515f538 | ||
|
|
42d75bd1ee | ||
|
|
296d7436ae | ||
|
|
5ba1466b96 | ||
|
|
7cdf36df87 | ||
|
|
02f25f8816 | ||
|
|
fa7e6bfa36 | ||
|
|
854890cc77 | ||
|
|
5445ec1b8d | ||
|
|
050f11bfab | ||
|
|
da89b95654 | ||
|
|
71631ccf0c | ||
|
|
f1c38dd742 | ||
|
|
0fc7126238 | ||
|
|
41cf366014 | ||
|
|
4288ae3582 | ||
|
|
e57d0a4d0e | ||
|
|
1b6a0188f3 | ||
|
|
e225b8a77c | ||
|
|
e4b1c542db | ||
|
|
b960460e02 | ||
|
|
c8f39802ae | ||
|
|
e3caad856d | ||
|
|
97bc512d1c | ||
|
|
5752fe698f | ||
|
|
727ef87a11 | ||
|
|
a92c85a0be | ||
|
|
225790cc4b | ||
|
|
0202d52e49 | ||
|
|
397ad4c597 | ||
|
|
ce00f31f0f | ||
|
|
b9dc9e6eb0 | ||
|
|
05884bba0d | ||
|
|
d8f37ac6a7 | ||
|
|
9c89e0b790 | ||
|
|
b37d695c8b | ||
|
|
263db54a12 | ||
|
|
dc8c25b8bc | ||
|
|
1d43cae1b1 | ||
|
|
7e02ae518d | ||
|
|
4afc439eb1 | ||
|
|
330d4f006f | ||
|
|
5929a7c6f9 | ||
|
|
1b95989b05 | ||
|
|
b284fc590e | ||
|
|
dd3e21e5d4 | ||
|
|
31c674c3dc | ||
|
|
b3632417a7 | ||
|
|
bc28919727 | ||
|
|
4333744248 | ||
|
|
d62599d62c | ||
|
|
bad745d2a9 | ||
|
|
0c6aa6afdb | ||
|
|
bb8e68d1b1 | ||
|
|
8c6b6e617c | ||
|
|
a9e4270ddb | ||
|
|
72262a77fd | ||
|
|
68c524a75d | ||
|
|
64c56288c9 | ||
|
|
2996bf7e6b | ||
|
|
5520c3778b | ||
|
|
4c6bdcb68a | ||
|
|
a925786899 | ||
|
|
e73a3cbf8c | ||
|
|
c86dbb4665 | ||
|
|
951c338edd | ||
|
|
5a22ad86b8 | ||
|
|
6db22d43a6 | ||
|
|
6569f59501 | ||
|
|
c30b04131b | ||
|
|
fb7e6d482a | ||
|
|
df252a0051 | ||
|
|
443d79f902 | ||
|
|
7ac9997950 | ||
|
|
aa59b5b4e3 | ||
|
|
2cd0b56f11 | ||
|
|
40c8485f02 | ||
|
|
f70e22fa4d | ||
|
|
cbaa7028d0 | ||
|
|
d6a4e33035 | ||
|
|
99bae93e07 | ||
|
|
9097fb6f60 | ||
|
|
8e9b91eeb6 | ||
|
|
419b46c7cc | ||
|
|
e927d76cb5 | ||
|
|
966d190b77 | ||
|
|
b7b06413d6 | ||
|
|
67ba5e21f7 | ||
|
|
27e401d3a1 | ||
|
|
45c1c40e9f | ||
|
|
29cfde5249 | ||
|
|
ffecb391b3 | ||
|
|
c87f8b0986 | ||
|
|
8cc4eef916 | ||
|
|
46f241f004 | ||
|
|
4dbe0bdbcb | ||
|
|
ee5e0beb7f | ||
|
|
ef89a35de6 | ||
|
|
26f94ccf07 | ||
|
|
c7a0b9d31c | ||
|
|
e2f91339b4 | ||
|
|
155b2723f1 | ||
|
|
2b5d5c5c12 | ||
|
|
660f854546 | ||
|
|
505ae78d14 | ||
|
|
0f19934021 | ||
|
|
0865e3262f | ||
|
|
8d266dbc4c | ||
|
|
8801365896 | ||
|
|
33249ea526 | ||
|
|
d1f3ec25fc | ||
|
|
8ea76ee1b8 | ||
|
|
660ed4c341 | ||
|
|
ef6f88a129 | ||
|
|
bf072e0802 | ||
|
|
d910521a0b | ||
|
|
3ceb40741c | ||
|
|
7d3a192435 | ||
|
|
50af4b83c5 | ||
|
|
2e4f79f6ef | ||
|
|
7c57be2181 | ||
|
|
2f238789ad | ||
|
|
29d4cfe669 | ||
|
|
df4eaa28b9 | ||
|
|
42dc55c664 | ||
|
|
a82ad37e78 | ||
|
|
5fdc7970cd | ||
|
|
8f45cc62d2 | ||
|
|
b8eaa369c2 | ||
|
|
fec2a4fcd5 | ||
|
|
f40ca2037b | ||
|
|
7227885e0e | ||
|
|
4fe66ddca8 | ||
|
|
5472f17c96 | ||
|
|
69b072f130 | ||
|
|
640106a78a | ||
|
|
2e31e1840a | ||
|
|
09334d2043 | ||
|
|
b52d22fd76 | ||
|
|
34233bf682 | ||
|
|
3ca111f6e0 | ||
|
|
09556cc3ac | ||
|
|
ff94d85e53 | ||
|
|
dd759d62e9 | ||
|
|
9739ed6b87 | ||
|
|
3d4c88ae7c | ||
|
|
eb5f842e7b | ||
|
|
6fb409dcd0 | ||
|
|
b2e36e8ca2 | ||
|
|
709524b7c9 | ||
|
|
4444438670 | ||
|
|
87ada19569 | ||
|
|
082fce33c3 | ||
|
|
ae93d2028a | ||
|
|
a36719686b | ||
|
|
981dc381cf | ||
|
|
261241c1ee | ||
|
|
7c8d432aa9 | ||
|
|
bd82d5d355 | ||
|
|
fed9270211 | ||
|
|
2fa0ef2572 | ||
|
|
02ffbb2b5c | ||
|
|
c00197d44a | ||
|
|
aa30390674 | ||
|
|
c54bf6f730 | ||
|
|
a5cc4cc01d | ||
|
|
ca02c342d7 | ||
|
|
bd58d81bcc | ||
|
|
15dfdb5bc1 | ||
|
|
36730dd6e2 | ||
|
|
0122589138 | ||
|
|
e82b1c4524 | ||
|
|
8a0ae86b28 | ||
|
|
d406277247 | ||
|
|
5e5015c552 | ||
|
|
22c46a50f0 | ||
|
|
7c9404864a | ||
|
|
82752f55d1 | ||
|
|
50a7643610 | ||
|
|
a85c84096f | ||
|
|
7edda12548 | ||
|
|
d91f1df9d8 | ||
|
|
7c2f9e529c | ||
|
|
d7ca06c599 | ||
|
|
f4ee0441ea | ||
|
|
7b28ea1fa3 | ||
|
|
e02da44288 | ||
|
|
e836880a39 | ||
|
|
2ef00808b7 | ||
|
|
1dca52e8cd | ||
|
|
a4f7bd4d2b | ||
|
|
5740917a11 | ||
|
|
0790220a44 | ||
|
|
462459c6c2 | ||
|
|
eee160c379 | ||
|
|
96c1ce5803 | ||
|
|
6dc705450b | ||
|
|
e9f5039817 | ||
|
|
4a7d0222a2 | ||
|
|
63e7803ea4 | ||
|
|
c522710f7f | ||
|
|
d9be2b2437 | ||
|
|
caeaefcc6a | ||
|
|
6048161924 | ||
|
|
4348bc20eb | ||
|
|
bff6d43701 | ||
|
|
944fc70ff4 | ||
|
|
a75a148278 | ||
|
|
a378eefb7b | ||
|
|
9782705e06 | ||
|
|
4cd47da3f4 | ||
|
|
65776d8401 | ||
|
|
1c5c5b3429 | ||
|
|
284d51918f | ||
|
|
06a1a49a73 | ||
|
|
1c2c72ca7c | ||
|
|
f2a28a34fe | ||
|
|
34e6eedf23 | ||
|
|
c1042d15b5 | ||
|
|
c40f258725 | ||
|
|
8619c180de | ||
|
|
1e9e5c5956 | ||
|
|
095769b7cd | ||
|
|
0740115724 | ||
|
|
862f2080ea | ||
|
|
2ed264b2b1 | ||
|
|
e9c2ea0e87 | ||
|
|
79cea28630 | ||
|
|
ec6ed8cea5 | ||
|
|
eab7a03c77 | ||
|
|
970d82e48e | ||
|
|
a9ad5169f2 | ||
|
|
27e0b3e62f | ||
|
|
3dfa2cd397 | ||
|
|
782eca7b95 | ||
|
|
17c6e46e8d | ||
|
|
684b3d2b2b | ||
|
|
e0c4ee102c | ||
|
|
0766a4b76d | ||
|
|
e16bd40fd3 | ||
|
|
479f72f160 | ||
|
|
56bfb32ad4 | ||
|
|
45dbb08993 | ||
|
|
5030f6e901 | ||
|
|
e64003a22f | ||
|
|
3abdeee850 | ||
|
|
dfec2dc308 | ||
|
|
abede2f979 | ||
|
|
695a0dac40 | ||
|
|
266e2e13cf | ||
|
|
79a81c86bb | ||
|
|
20f7bf5ebf | ||
|
|
bc3cd33a30 | ||
|
|
5eb3902854 | ||
|
|
eb75911d50 | ||
|
|
81b352c888 | ||
|
|
4b64dff6dd | ||
|
|
ea2eb39975 | ||
|
|
c3d64a7d2a | ||
|
|
4049a66c45 | ||
|
|
b06749aff9 | ||
|
|
6fa418b43b | ||
|
|
831786f55e | ||
|
|
952a9b69e1 | ||
|
|
8d367a1167 | ||
|
|
6cb366a978 | ||
|
|
aa99373d2d | ||
|
|
a6633365ec | ||
|
|
cac0f46d1c | ||
|
|
3e3b14779e | ||
|
|
225767acab | ||
|
|
7d1f31247e | ||
|
|
5fe1de5f7f | ||
|
|
0ac4f98ffa | ||
|
|
ebd3c72170 | ||
|
|
839a91d144 | ||
|
|
5c3df787bb | ||
|
|
daeec63b71 | ||
|
|
1aea0aa24c | ||
|
|
7b53957e55 | ||
|
|
09c7af4cc4 | ||
|
|
7ed9526828 | ||
|
|
27c61f1596 | ||
|
|
ca8e4d19bf | ||
|
|
938114f942 | ||
|
|
b7c61baafb | ||
|
|
5850e271c3 | ||
|
|
7ab15776df | ||
|
|
7f0b819993 | ||
|
|
c0af1fa1eb | ||
|
|
316eb681f6 | ||
|
|
2a841e4ae2 | ||
|
|
0033de0831 | ||
|
|
f70af84e16 | ||
|
|
d9571f3c4d | ||
|
|
fda2f1ff15 | ||
|
|
0d4824b7e0 | ||
|
|
0cd376e039 | ||
|
|
101c953020 | ||
|
|
46bf18cbfa | ||
|
|
5348cd3f22 | ||
|
|
9591ed744b | ||
|
|
efed8bbd4b | ||
|
|
7e0025d3fc | ||
|
|
b016b56a30 | ||
|
|
167d577f4d | ||
|
|
8888c97e07 | ||
|
|
b99e1f7b28 | ||
|
|
39b09c5b25 | ||
|
|
86842735bf | ||
|
|
05cf67d3e1 | ||
|
|
9f10d1b923 | ||
|
|
db66c6cde4 | ||
|
|
0eaa99a91a | ||
|
|
11de057064 | ||
|
|
d206ed5c0b | ||
|
|
6dd0275e5a | ||
|
|
cefaa78832 | ||
|
|
7253c56086 | ||
|
|
38dc4274ff | ||
|
|
833a5bd974 | ||
|
|
2443772e78 | ||
|
|
243a24c7f2 | ||
|
|
1166297dda | ||
|
|
58bfc4b36c | ||
|
|
1b07c8838f | ||
|
|
6592694778 | ||
|
|
08d5735af9 | ||
|
|
4cd69a52f6 | ||
|
|
542bab816f | ||
|
|
357be5296f | ||
|
|
57816a4f3a | ||
|
|
7486eb640f | ||
|
|
e946e23360 | ||
|
|
e233eed8eb | ||
|
|
80ac8906b3 | ||
|
|
ee9cb656fc | ||
|
|
cfe9f8e6e6 | ||
|
|
72f768cca4 | ||
|
|
9d0206c432 | ||
|
|
53bc91d399 | ||
|
|
ca63763d04 | ||
|
|
ac7589a3ff | ||
|
|
d752442aa3 | ||
|
|
930c27a890 | ||
|
|
2caec7be0e | ||
|
|
a7cfe98ca3 | ||
|
|
bad5f6f431 | ||
|
|
d0116c852c | ||
|
|
3f3ce39b30 | ||
|
|
8a5692e9df | ||
|
|
5cf96b1154 | ||
|
|
2d392de9ef | ||
|
|
f53aff1882 | ||
|
|
0cf6cbf3bc | ||
|
|
28b428a6f1 | ||
|
|
d311ced441 | ||
|
|
3ccce18f23 | ||
|
|
0ecd719799 | ||
|
|
5438e20b01 | ||
|
|
b5f72ecc5c | ||
|
|
dd20c8caf0 | ||
|
|
c3b96e4f68 | ||
|
|
b3132aa757 | ||
|
|
7685e3233f | ||
|
|
ee3e66580d | ||
|
|
26cd2acbcd | ||
|
|
0c3b099265 | ||
|
|
6971d320ae | ||
|
|
2dd6305e54 | ||
|
|
baf5479d0b | ||
|
|
c57be04143 | ||
|
|
f1030c2f10 | ||
|
|
1f1639e661 | ||
|
|
04d3445834 | ||
|
|
1559a14e39 | ||
|
|
fbbc6aa005 | ||
|
|
9c573b9bfd | ||
|
|
96bbe2c7dc | ||
|
|
ec9f6b82d9 | ||
|
|
2c120c3c45 | ||
|
|
b911f5c45b | ||
|
|
6366a22ff3 | ||
|
|
5fc819d8f5 | ||
|
|
df9d1ddcbf | ||
|
|
eed7b282d3 | ||
|
|
fc6237cd29 | ||
|
|
c473e634e0 | ||
|
|
28866fc1dc | ||
|
|
8028e8ca12 | ||
|
|
28d1cc2055 | ||
|
|
cffd99b68d | ||
|
|
ac92a92090 | ||
|
|
28dbb309cf | ||
|
|
7b585aaa05 | ||
|
|
82f0c85bd0 | ||
|
|
1c0a7d8475 | ||
|
|
5f63b5f907 | ||
|
|
7459cd65d9 | ||
|
|
0b61ddaff7 | ||
|
|
d554a7ee97 | ||
|
|
3d1e130b1e | ||
|
|
a63e008f84 | ||
|
|
d76ae27611 | ||
|
|
bf00d79e23 | ||
|
|
c08775f3e3 | ||
|
|
d94ceaf492 | ||
|
|
9802cb62d9 | ||
|
|
2c4f90b31c | ||
|
|
163d95800b | ||
|
|
0662d44a86 | ||
|
|
c3792191a2 | ||
|
|
97ff18c3e2 | ||
|
|
d069252f76 | ||
|
|
54fda151be | ||
|
|
04c4cf4426 | ||
|
|
7c81fe3339 | ||
|
|
330862e6c0 | ||
|
|
ce309a384e | ||
|
|
6bc01c3472 | ||
|
|
9ecf3e11bf | ||
|
|
7da306eb65 | ||
|
|
3d9d6d45fe | ||
|
|
7fc85a47be | ||
|
|
6accf7fd6c | ||
|
|
f619155667 | ||
|
|
89d50af521 | ||
|
|
fe3a111d6f | ||
|
|
75b6b6316b | ||
|
|
f64822a18b | ||
|
|
e566fb2d57 | ||
|
|
9f5931509c | ||
|
|
deb7ee8c3a | ||
|
|
6a56e29474 | ||
|
|
c79ccd7e23 | ||
|
|
36e395e3a3 | ||
|
|
c0b106c7ef | ||
|
|
23c8c5da19 | ||
|
|
883f1a7cf8 | ||
|
|
b2ff43bd9b | ||
|
|
471bb1cacd | ||
|
|
de30329a58 | ||
|
|
c990c8af56 | ||
|
|
2dbd099c68 | ||
|
|
25da363d56 | ||
|
|
a957566fc9 | ||
|
|
97db42427e | ||
|
|
34fabaab5f | ||
|
|
11893e244e | ||
|
|
24f1285a43 | ||
|
|
2d2e0fd6c0 | ||
|
|
5ea2aca8a8 | ||
|
|
088b4fa07e | ||
|
|
7dcfbc409c | ||
|
|
44bb2ff0f5 | ||
|
|
4effb02219 | ||
|
|
5d7ad6bb81 | ||
|
|
2bef5d7653 | ||
|
|
6b76bec480 | ||
|
|
76ab53078e | ||
|
|
ddf8df9c71 | ||
|
|
b312f28ab6 | ||
|
|
d9e536b789 | ||
|
|
2e768cb77b | ||
|
|
d611dbd386 | ||
|
|
8976e238a5 | ||
|
|
eb13bb8b6d | ||
|
|
d72eba1602 | ||
|
|
cd4685c093 | ||
|
|
bc3aa56ed9 | ||
|
|
903ead9e91 | ||
|
|
bacbd8baf3 | ||
|
|
407c742124 | ||
|
|
34a91b7e3d | ||
|
|
65f4724f87 | ||
|
|
afafe46221 | ||
|
|
ff220113af | ||
|
|
81c2c482cd | ||
|
|
afb06baa56 | ||
|
|
d25ff61f22 | ||
|
|
067f0b3a66 | ||
|
|
0f71efd1e9 | ||
|
|
351f9c1976 | ||
|
|
a11eff4d9a | ||
|
|
fe6a5a700a | ||
|
|
87a994b8ce | ||
|
|
3f06553604 | ||
|
|
e9f765a216 | ||
|
|
532e65f3a0 | ||
|
|
b038044347 | ||
|
|
0664d11f55 | ||
|
|
6d00a26530 | ||
|
|
a4d28b13ef | ||
|
|
fd01dc1bb1 | ||
|
|
fa72d3d0de | ||
|
|
f935f57d23 | ||
|
|
fbf1e855ad | ||
|
|
50a6037a6d | ||
|
|
d15d27defb | ||
|
|
7952bdcb48 | ||
|
|
dfdf3160d3 | ||
|
|
ce4f12179c | ||
|
|
491da787b4 | ||
|
|
6dbc7e2704 | ||
|
|
2a39c917ee | ||
|
|
3733ba45e2 | ||
|
|
2a410b9099 | ||
|
|
3ccc7c41e7 | ||
|
|
d03bc797fc | ||
|
|
5c4f8eb526 | ||
|
|
973464f1c4 | ||
|
|
e090ab2825 | ||
|
|
0413e5ded8 | ||
|
|
51c24f6fdf | ||
|
|
d1de248894 | ||
|
|
fdf3e618c2 | ||
|
|
5b0a228f4f | ||
|
|
9db11ce690 | ||
|
|
f9bdf6e181 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
.vscode/launch.json
|
.vscode/launch.json
|
||||||
|
.factorypath
|
||||||
|
|
||||||
|
|
||||||
# Ignore NetBeans config files
|
# Ignore NetBeans config files
|
||||||
@@ -32,6 +33,8 @@ bin
|
|||||||
gen
|
gen
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
# Ignore macOS Spotlight rubbish
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
# TODO: specify what these ignores are for (releasing?)
|
# TODO: specify what these ignores are for (releasing?)
|
||||||
|
|
||||||
|
|||||||
86
README.md
86
README.md
@@ -1,15 +1,15 @@
|
|||||||
# Forge
|
# Forge
|
||||||
|
|
||||||
Gitlab repo is found [here](https://git.cardforge.org/core-developers/forge).
|
[Official GitLab repo](https://git.cardforge.org/core-developers/forge).
|
||||||
|
|
||||||
Dev instructions here: [Getting Started](https://www.slightlymagic.net/wiki/Forge:How_to_Get_Started_Developing_Forge) (Somewhat outdated)
|
Dev instructions here: [Getting Started](https://git.cardforge.org/core-developers/forge/-/wikis/(SM-autoconverted)-how-to-get-started-developing-forge) (Somewhat outdated)
|
||||||
|
|
||||||
Discord channel [here](https://discordapp.com/channels/267367946135928833/267742313390931968)
|
Discord channel [here](https://discordapp.com/channels/267367946135928833/267742313390931968)
|
||||||
|
|
||||||
# Requirements / Tools
|
## Requirements / Tools
|
||||||
|
|
||||||
- Java IDE such as IntelliJ or Eclipse
|
- you favourite Java IDE (IntelliJ, Eclipse, VSCodium, Emacs, Vi...)
|
||||||
- Java JDK 8 or later
|
- Java JDK 8 or later (some IDEs such as Eclipse require JDK11+, whereas the Android build currently only works with JDK8)
|
||||||
- Git
|
- Git
|
||||||
- Git client (optional)
|
- Git client (optional)
|
||||||
- Maven
|
- Maven
|
||||||
@@ -18,7 +18,7 @@ Discord channel [here](https://discordapp.com/channels/267367946135928833/267742
|
|||||||
- Android SDK (optional: for Android releases)
|
- Android SDK (optional: for Android releases)
|
||||||
- RoboVM (optional: for iOS releases) (TBD: Current status of support by libgdx)
|
- RoboVM (optional: for iOS releases) (TBD: Current status of support by libgdx)
|
||||||
|
|
||||||
# Project Quick Setup
|
## Project Quick Setup
|
||||||
|
|
||||||
- Log in to gitlab with your user account and fork the project.
|
- Log in to gitlab with your user account and fork the project.
|
||||||
|
|
||||||
@@ -26,11 +26,11 @@ Discord channel [here](https://discordapp.com/channels/267367946135928833/267742
|
|||||||
|
|
||||||
- Go to the project location on your machine. Run Maven to download all dependencies and build a snapshot. Example for Windows & Linux: `mvn -U -B clean -P windows-linux install`
|
- Go to the project location on your machine. Run Maven to download all dependencies and build a snapshot. Example for Windows & Linux: `mvn -U -B clean -P windows-linux install`
|
||||||
|
|
||||||
# Eclipse
|
## Eclipse
|
||||||
|
|
||||||
Eclipse includes Maven integration so a separate install is not necessary. For other IDEs, your mileage may vary.
|
Eclipse includes Maven integration so a separate install is not necessary. For other IDEs, your mileage may vary.
|
||||||
|
|
||||||
## Project Setup
|
### Project Setup
|
||||||
|
|
||||||
- Follow the instructions for cloning from Gitlab. You'll need a Gitlab account setup and an SSH key defined.
|
- Follow the instructions for cloning from Gitlab. You'll need a Gitlab account setup and an SSH key defined.
|
||||||
|
|
||||||
@@ -55,9 +55,9 @@ Eclipse includes Maven integration so a separate install is not necessary. For
|
|||||||
|
|
||||||
- Once everything builds, all errors should disappear. You can now advance to Project launch.
|
- Once everything builds, all errors should disappear. You can now advance to Project launch.
|
||||||
|
|
||||||
## Project Launch
|
### Project Launch
|
||||||
|
|
||||||
### Desktop
|
#### Desktop
|
||||||
|
|
||||||
This is the standard configuration used for releasing to Windows / Linux / MacOS.
|
This is the standard configuration used for releasing to Windows / Linux / MacOS.
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ This is the standard configuration used for releasing to Windows / Linux / MacOS
|
|||||||
|
|
||||||
- The familiar Forge splash screen, etc. should appear. Enjoy!
|
- The familiar Forge splash screen, etc. should appear. Enjoy!
|
||||||
|
|
||||||
### Mobile (Desktop dev)
|
#### Mobile (Desktop dev)
|
||||||
|
|
||||||
This is the configuration used for doing mobile development using the Windows / Linux / MacOS front-end. Knowledge of libgdx is helpful here.
|
This is the configuration used for doing mobile development using the Windows / Linux / MacOS front-end. Knowledge of libgdx is helpful here.
|
||||||
|
|
||||||
@@ -73,24 +73,24 @@ This is the configuration used for doing mobile development using the Windows /
|
|||||||
|
|
||||||
- A view similar to a mobile phone should appear. Enjoy!
|
- A view similar to a mobile phone should appear. Enjoy!
|
||||||
|
|
||||||
## Eclipse / Android SDK Integration
|
### Eclipse / Android SDK Integration
|
||||||
|
|
||||||
Google no longer supports Android SDK releases for Eclipse. That said, it is still possible to build and debug Android platforms.
|
Google no longer supports Android SDK releases for Eclipse. That said, it is still possible to build and debug Android platforms.
|
||||||
|
|
||||||
### Android SDK
|
#### Android SDK
|
||||||
|
|
||||||
Reference SO for obtaining a specific release: https://stackoverflow.com/questions/27043522/where-can-i-download-an-older-version-of-the-android-sdk
|
Reference SO for obtaining a specific release: https://stackoverflow.com/questions/27043522/where-can-i-download-an-older-version-of-the-android-sdk
|
||||||
|
|
||||||
#### Windows
|
##### Windows
|
||||||
|
|
||||||
Download the following archived version of the Android SDK: http://dl-ssl.google.com/android/repository/tools_r25.2.3-windows.zip. Install it somewhere on your machine. This is referenced
|
Download the following archived version of the Android SDK: http://dl-ssl.google.com/android/repository/tools_r25.2.3-windows.zip. Install it somewhere on your machine. This is referenced
|
||||||
in the following instructions as your 'Android SDK Install' path.
|
in the following instructions as your 'Android SDK Install' path.
|
||||||
|
|
||||||
#### Linux / Mac OSX
|
##### Linux / Mac OSX
|
||||||
|
|
||||||
TBD
|
TBD
|
||||||
|
|
||||||
### Android Plugin for Eclipse
|
#### Android Plugin for Eclipse
|
||||||
|
|
||||||
Google's last plugin release does not work completely with target's running Android 7.0 or later. Download the ADT-24.2.0-20160729.zip plugin
|
Google's last plugin release does not work completely with target's running Android 7.0 or later. Download the ADT-24.2.0-20160729.zip plugin
|
||||||
from: https://github.com/khaledev/ADT/releases
|
from: https://github.com/khaledev/ADT/releases
|
||||||
@@ -98,25 +98,24 @@ from: https://github.com/khaledev/ADT/releases
|
|||||||
In Eclipse go to: Help > Install New Software... > Add > Name: ADT Update, Click on the "Archive:" button and navigate to the downloaded ADT-24.2.0-20160729.zip file > Add. Install all "Developer Tools". Eclipse
|
In Eclipse go to: Help > Install New Software... > Add > Name: ADT Update, Click on the "Archive:" button and navigate to the downloaded ADT-24.2.0-20160729.zip file > Add. Install all "Developer Tools". Eclipse
|
||||||
should restart and prompt you to run the SDK Manager. Launch it and continue to the next steps below.
|
should restart and prompt you to run the SDK Manager. Launch it and continue to the next steps below.
|
||||||
|
|
||||||
### Android Platform
|
#### Android Platform
|
||||||
|
|
||||||
In Eclipse, if the SDK Manager is not already running, go to Window > Android SDK Manager. Install the following options / versions:
|
In Eclipse, if the SDK Manager is not already running, go to Window > Android SDK Manager. Install the following options / versions:
|
||||||
|
|
||||||
- Android SDK Build-tools 26.0.1
|
- Android SDK Build-tools 26.0.1
|
||||||
- Android 7.1.1 (API 25) SDK Platform
|
- Android 8.0.0 (API 26) SDK Platform
|
||||||
- Google USB Driver 11
|
- Google USB Driver (in case your phone is not detected by ADB)
|
||||||
|
|
||||||
Note that this will populate additional tools in the Android SDK install path extracted above.
|
Note that this will populate additional tools in the Android SDK install path extracted above.
|
||||||
|
|
||||||
### Proguard update
|
#### Proguard update
|
||||||
|
|
||||||
The Proguard included with the Android SDK Build-tools is outdated and does not work with Java 1.8. Download Proguard 6.0.3 from https://sourceforge.net/projects/proguard/files/proguard/6.0/.
|
The Proguard included with the Android SDK Build-tools is outdated and does not work with Java 1.8. Download Proguard 6.0.3 or later (last tested with 7.0.1) from https://github.com/Guardsquare/proguard
|
||||||
|
- Go to the Android SDK install path. Rename the tools/proguard/ path to tools/proguard-4.7/.
|
||||||
|
|
||||||
- Go to the Android SDK install path. Rename the tools/proguard/ path to tools/proguard4.7/.
|
- Extract your Proguard version to the Android SDK install path under tools/. You will need to either rename the dir proguard-<your-version> to proguard/ or, if your filesystem supports it, use a symbolic link (the later is highly recommended), such as `ln -s proguard proguard-<your-version>`.
|
||||||
|
|
||||||
- Extract Proguard 6.0.3 to the Android SDK install path under tools/. You will need to rename the dir proguard6.0.3/ to proguard/.
|
#### Android Build
|
||||||
|
|
||||||
### Android Build
|
|
||||||
|
|
||||||
The Eclipse plug-ins do NOT support building things for Android. They do however allow you to use the debugger so you can still set breakpoints and trace
|
The Eclipse plug-ins do NOT support building things for Android. They do however allow you to use the debugger so you can still set breakpoints and trace
|
||||||
things out. The steps below show how to generate a debug Android build.
|
things out. The steps below show how to generate a debug Android build.
|
||||||
@@ -135,7 +134,7 @@ things out. The steps below show how to generate a debug Android build.
|
|||||||
|
|
||||||
Assuming you got this far, you should have an Android forge-android-[version].apk in the forge-gui-android/target path.
|
Assuming you got this far, you should have an Android forge-android-[version].apk in the forge-gui-android/target path.
|
||||||
|
|
||||||
### Android Deploy
|
#### Android Deploy
|
||||||
|
|
||||||
You'll need to have the Android SDK install path platform-tools/ path in your command search path to easily deploy builds.
|
You'll need to have the Android SDK install path platform-tools/ path in your command search path to easily deploy builds.
|
||||||
|
|
||||||
@@ -149,14 +148,14 @@ You'll need to have the Android SDK install path platform-tools/ path in your co
|
|||||||
|
|
||||||
- Install the new apk: `adb install forge-android-[version].apk`
|
- Install the new apk: `adb install forge-android-[version].apk`
|
||||||
|
|
||||||
### Android Debugging
|
#### Android Debugging
|
||||||
|
|
||||||
Assuming the apk is installed, launch it from the device.
|
Assuming the apk is installed, launch it from the device.
|
||||||
|
|
||||||
In Eclipse, launch the DDMS. Window > Perspective > Open Perspective > Other... > DDMS. You should see the forge app in the list. Highlight the app, click on the green debug button and a
|
In Eclipse, launch the DDMS. Window > Perspective > Open Perspective > Other... > DDMS. You should see the forge app in the list. Highlight the app, click on the green debug button and a
|
||||||
green debug button should appear next to the app's name. You can now set breakpoints and step through the source code.
|
green debug button should appear next to the app's name. You can now set breakpoints and step through the source code.
|
||||||
|
|
||||||
## Windows / Linux SNAPSHOT build
|
### Windows / Linux SNAPSHOT build
|
||||||
|
|
||||||
SNAPSHOT builds can be built via the Maven integration in Eclipse.
|
SNAPSHOT builds can be built via the Maven integration in Eclipse.
|
||||||
|
|
||||||
@@ -167,19 +166,19 @@ SNAPSHOT builds can be built via the Maven integration in Eclipse.
|
|||||||
|
|
||||||
The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desktop-[version]-SNAPSHOT
|
The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desktop-[version]-SNAPSHOT
|
||||||
|
|
||||||
# IntelliJ
|
## IntelliJ
|
||||||
|
|
||||||
Quick start guide for [setting up the Forge project within IntelliJ](https://git.cardforge.org/core-developers/forge/wikis/intellij-setup).
|
Quick start guide for [setting up the Forge project within IntelliJ](https://git.cardforge.org/core-developers/forge/-/wikis/Development/intellij-setup).
|
||||||
|
|
||||||
# Card Scripting
|
## Card Scripting
|
||||||
|
|
||||||
Visit [this page](https://www.slightlymagic.net/wiki/Forge_API) for information on scripting.
|
Visit [this page](https://git.cardforge.org/core-developers/forge/-/wikis/Card-scripting-API/Card-scripting-API) for information on scripting.
|
||||||
|
|
||||||
Card scripting resources are found in the forge-gui/res/ path.
|
Card scripting resources are found in the forge-gui/res/ path.
|
||||||
|
|
||||||
# General Notes
|
## General Notes
|
||||||
|
|
||||||
## Project Hierarchy
|
### Project Hierarchy
|
||||||
|
|
||||||
Forge is divided into 4 primary projects with additional projects that target specific platform releases. The primary projects are:
|
Forge is divided into 4 primary projects with additional projects that target specific platform releases. The primary projects are:
|
||||||
|
|
||||||
@@ -196,35 +195,34 @@ The platform-specific projects are:
|
|||||||
- forge-gui-mobile
|
- forge-gui-mobile
|
||||||
- forge-gui-mobile-dev
|
- forge-gui-mobile-dev
|
||||||
|
|
||||||
### forge-ai
|
#### forge-ai
|
||||||
|
|
||||||
### forge-core
|
#### forge-core
|
||||||
|
|
||||||
### forge-game
|
#### forge-game
|
||||||
|
|
||||||
### forge-gui
|
#### forge-gui
|
||||||
|
|
||||||
The forge-gui project includes the scripting resource definitions in the res/ path.
|
The forge-gui project includes the scripting resource definitions in the res/ path.
|
||||||
|
|
||||||
### forge-gui-android
|
#### forge-gui-android
|
||||||
|
|
||||||
Libgdx-based backend targeting Android. Requires Android SDK and relies on forge-gui-mobile for GUI logic.
|
Libgdx-based backend targeting Android. Requires Android SDK and relies on forge-gui-mobile for GUI logic.
|
||||||
|
|
||||||
### forge-gui-desktop
|
#### forge-gui-desktop
|
||||||
|
|
||||||
Java Swing based GUI targeting desktop machines.
|
Java Swing based GUI targeting desktop machines.
|
||||||
|
|
||||||
Screen layout and game logic revolving around the GUI is found here. For example, the overlay arrows (when enabled) that indicate attackers and blockers, or the targets of the stack are defined and drawn by this.
|
Screen layout and game logic revolving around the GUI is found here. For example, the overlay arrows (when enabled) that indicate attackers and blockers, or the targets of the stack are defined and drawn by this.
|
||||||
|
|
||||||
### forge-gui-ios
|
#### forge-gui-ios
|
||||||
|
|
||||||
Libgdx-based backend targeting iOS. Relies on forge-gui-mobile for GUI logic.
|
Libgdx-based backend targeting iOS. Relies on forge-gui-mobile for GUI logic.
|
||||||
|
|
||||||
### forge-gui-mobile
|
#### forge-gui-mobile
|
||||||
|
|
||||||
Mobile GUI game logic utilizing [libgdx](https://libgdx.badlogicgames.com/) library. Screen layout and game logic revolving around the GUI for the mobile platforms is found here.
|
Mobile GUI game logic utilizing [libgdx](https://libgdx.badlogicgames.com/) library. Screen layout and game logic revolving around the GUI for the mobile platforms is found here.
|
||||||
|
|
||||||
### forge-gui-mobile-dev
|
#### forge-gui-mobile-dev
|
||||||
|
|
||||||
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.
|
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>1.6.40-SNAPSHOT</version>
|
<version>1.6.44</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-ai</artifactId>
|
<artifactId>forge-ai</artifactId>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
@@ -43,6 +44,7 @@ import forge.game.combat.GlobalAttackRestrictions;
|
|||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.keyword.KeywordInterface;
|
import forge.game.keyword.KeywordInterface;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
@@ -54,7 +56,6 @@ import forge.util.TextUtil;
|
|||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
|
|
||||||
|
|
||||||
//doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer()
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* ComputerUtil_Attack2 class.
|
* ComputerUtil_Attack2 class.
|
||||||
@@ -91,7 +92,7 @@ public class AiAttackController {
|
|||||||
|
|
||||||
public AiAttackController(final Player ai, boolean nextTurn) {
|
public AiAttackController(final Player ai, boolean nextTurn) {
|
||||||
this.ai = ai;
|
this.ai = ai;
|
||||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
this.defendingOpponent = choosePreferredDefenderPlayer(ai);
|
||||||
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||||
this.myList = ai.getCreaturesInPlay();
|
this.myList = ai.getCreaturesInPlay();
|
||||||
this.attackers = new ArrayList<>();
|
this.attackers = new ArrayList<>();
|
||||||
@@ -107,7 +108,7 @@ public class AiAttackController {
|
|||||||
|
|
||||||
public AiAttackController(final Player ai, Card attacker) {
|
public AiAttackController(final Player ai, Card attacker) {
|
||||||
this.ai = ai;
|
this.ai = ai;
|
||||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
this.defendingOpponent = choosePreferredDefenderPlayer(ai);
|
||||||
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||||
this.myList = ai.getCreaturesInPlay();
|
this.myList = ai.getCreaturesInPlay();
|
||||||
this.attackers = new ArrayList<>();
|
this.attackers = new ArrayList<>();
|
||||||
@@ -156,13 +157,12 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Choose opponent for AI to attack here. Expand as necessary. */
|
/** Choose opponent for AI to attack here. Expand as necessary. */
|
||||||
private Player choosePreferredDefenderPlayer() {
|
public static Player choosePreferredDefenderPlayer(Player ai) {
|
||||||
Player defender = ai.getWeakestOpponent(); //Gets opponent with the least life
|
Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range
|
||||||
|
|
||||||
if (defender.getLife() < 8) { //Concentrate on opponent within easy kill range
|
if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players
|
||||||
return defender;
|
// TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked
|
||||||
} else { //Otherwise choose a random opponent to ensure no ganging up on players
|
return ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
|
||||||
defender = ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
|
|
||||||
}
|
}
|
||||||
return defender;
|
return defender;
|
||||||
}
|
}
|
||||||
@@ -393,7 +393,7 @@ public class AiAttackController {
|
|||||||
|
|
||||||
//Calculate the amount of creatures necessary
|
//Calculate the amount of creatures necessary
|
||||||
for (int i = 0; i < list.size(); i++) {
|
for (int i = 0; i < list.size(); i++) {
|
||||||
if (!this.doesHumanAttackAndWin(ai, i)) {
|
if (!doesHumanAttackAndWin(ai, i)) {
|
||||||
blockersNeeded = i;
|
blockersNeeded = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -413,12 +413,10 @@ public class AiAttackController {
|
|||||||
|
|
||||||
final Player opp = this.defendingOpponent;
|
final Player opp = this.defendingOpponent;
|
||||||
|
|
||||||
// Increase the total number of blockers needed by 1 if Finest Hour in
|
// Increase the total number of blockers needed by 1 if Finest Hour in play
|
||||||
// play
|
|
||||||
// (human will get an extra first attack with a creature that untaps)
|
// (human will get an extra first attack with a creature that untaps)
|
||||||
// In addition, if the computer guesses it needs no blockers, make sure
|
// In addition, if the computer guesses it needs no blockers, make sure
|
||||||
// that
|
// that it won't be surprised by Exalted
|
||||||
// it won't be surprised by Exalted
|
|
||||||
final int humanExaltedBonus = opp.countExaltedBonus();
|
final int humanExaltedBonus = opp.countExaltedBonus();
|
||||||
|
|
||||||
if (humanExaltedBonus > 0) {
|
if (humanExaltedBonus > 0) {
|
||||||
@@ -428,8 +426,7 @@ public class AiAttackController {
|
|||||||
// total attack = biggest creature + exalted, *2 if Rafiq is in play
|
// total attack = biggest creature + exalted, *2 if Rafiq is in play
|
||||||
int humanBasePower = getAttack(this.oppList.get(0)) + humanExaltedBonus;
|
int humanBasePower = getAttack(this.oppList.get(0)) + humanExaltedBonus;
|
||||||
if (finestHour) {
|
if (finestHour) {
|
||||||
// For Finest Hour, one creature could attack and get the
|
// For Finest Hour, one creature could attack and get the bonus TWICE
|
||||||
// bonus TWICE
|
|
||||||
humanBasePower = humanBasePower + humanExaltedBonus;
|
humanBasePower = humanBasePower + humanExaltedBonus;
|
||||||
}
|
}
|
||||||
final int totalExaltedAttack = opp.isCardInPlay("Rafiq of the Many") ? 2 * humanBasePower
|
final int totalExaltedAttack = opp.isCardInPlay("Rafiq of the Many") ? 2 * humanBasePower
|
||||||
@@ -450,7 +447,6 @@ public class AiAttackController {
|
|||||||
return notNeededAsBlockers;
|
return notNeededAsBlockers;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this uses a global variable, which isn't perfect
|
|
||||||
public final boolean doesHumanAttackAndWin(final Player ai, final int nBlockingCreatures) {
|
public final boolean doesHumanAttackAndWin(final Player ai, final int nBlockingCreatures) {
|
||||||
int totalAttack = 0;
|
int totalAttack = 0;
|
||||||
int totalPoison = 0;
|
int totalPoison = 0;
|
||||||
@@ -624,7 +620,7 @@ public class AiAttackController {
|
|||||||
int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage;
|
int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage;
|
||||||
int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, opp);
|
int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, opp);
|
||||||
|
|
||||||
if (totalCombatDamage + ComputerUtil.possibleNonCombatDamage(ai) >= opp.getLife()
|
if (totalCombatDamage + ComputerUtil.possibleNonCombatDamage(ai, opp) >= opp.getLife()
|
||||||
&& !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && opp.getLife() < 1)) {
|
&& !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && opp.getLife() < 1)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -641,7 +637,7 @@ public class AiAttackController {
|
|||||||
if (defs.size() == 1) {
|
if (defs.size() == 1) {
|
||||||
return defs.getFirst();
|
return defs.getFirst();
|
||||||
}
|
}
|
||||||
Player prefDefender = (Player) (defs.contains(this.defendingOpponent) ? this.defendingOpponent : defs.get(0));
|
GameEntity prefDefender = defs.contains(this.defendingOpponent) ? this.defendingOpponent : defs.get(0);
|
||||||
|
|
||||||
// Attempt to see if there's a defined entity that must be attacked strictly this turn...
|
// Attempt to see if there's a defined entity that must be attacked strictly this turn...
|
||||||
GameEntity entity = ai.getMustAttackEntityThisTurn();
|
GameEntity entity = ai.getMustAttackEntityThisTurn();
|
||||||
@@ -665,7 +661,8 @@ public class AiAttackController {
|
|||||||
// 2. attack planeswalkers
|
// 2. attack planeswalkers
|
||||||
List<Card> pwDefending = c.getDefendingPlaneswalkers();
|
List<Card> pwDefending = c.getDefendingPlaneswalkers();
|
||||||
if (!pwDefending.isEmpty()) {
|
if (!pwDefending.isEmpty()) {
|
||||||
return pwDefending.get(0);
|
final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending);
|
||||||
|
return pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending);
|
||||||
} else {
|
} else {
|
||||||
return prefDefender;
|
return prefDefender;
|
||||||
}
|
}
|
||||||
@@ -682,7 +679,6 @@ public class AiAttackController {
|
|||||||
* @return a {@link forge.game.combat.Combat} object.
|
* @return a {@link forge.game.combat.Combat} object.
|
||||||
*/
|
*/
|
||||||
public final void declareAttackers(final Combat combat) {
|
public final void declareAttackers(final Combat combat) {
|
||||||
|
|
||||||
if (this.attackers.isEmpty()) {
|
if (this.attackers.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -702,14 +698,14 @@ public class AiAttackController {
|
|||||||
tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
|
tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean bAssault = this.doAssault(ai);
|
final boolean bAssault = doAssault(ai);
|
||||||
// TODO: detect Lightmine Field by presence of a card with a specific trigger
|
// TODO: detect Lightmine Field by presence of a card with a specific trigger
|
||||||
final boolean lightmineField = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Lightmine Field");
|
final boolean lightmineField = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Lightmine Field");
|
||||||
// TODO: detect Season of the Witch by presence of a card with a specific trigger
|
// TODO: detect Season of the Witch by presence of a card with a specific trigger
|
||||||
final boolean seasonOfTheWitch = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Season of the Witch");
|
final boolean seasonOfTheWitch = ComputerUtilCard.isPresentOnBattlefield(ai.getGame(), "Season of the Witch");
|
||||||
|
|
||||||
// Determine who will be attacked
|
// Determine who will be attacked
|
||||||
GameEntity defender = this.chooseDefender(combat, bAssault);
|
GameEntity defender = chooseDefender(combat, bAssault);
|
||||||
List<Card> attackersLeft = new ArrayList<>(this.attackers);
|
List<Card> attackersLeft = new ArrayList<>(this.attackers);
|
||||||
|
|
||||||
// TODO probably use AttackConstraints instead of only GlobalAttackRestrictions?
|
// TODO probably use AttackConstraints instead of only GlobalAttackRestrictions?
|
||||||
@@ -717,7 +713,7 @@ public class AiAttackController {
|
|||||||
int attackMax = restrict.getMax();
|
int attackMax = restrict.getMax();
|
||||||
if (attackMax == -1) {
|
if (attackMax == -1) {
|
||||||
// check with the local limitations vs. the chosen defender
|
// check with the local limitations vs. the chosen defender
|
||||||
attackMax = ComputerUtilCombat.getMaxAttackersFor(defender);
|
attackMax = restrict.getDefenderMax().get(defender) == null ? -1 : restrict.getDefenderMax().get(defender);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attackMax == 0) {
|
if (attackMax == 0) {
|
||||||
@@ -777,7 +773,6 @@ public class AiAttackController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (bAssault) {
|
if (bAssault) {
|
||||||
if (LOG_AI_ATTACKS)
|
if (LOG_AI_ATTACKS)
|
||||||
System.out.println("Assault");
|
System.out.println("Assault");
|
||||||
@@ -852,7 +847,6 @@ public class AiAttackController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// *******************
|
// *******************
|
||||||
// Evaluate the creature forces
|
// Evaluate the creature forces
|
||||||
// *******************
|
// *******************
|
||||||
@@ -919,7 +913,7 @@ public class AiAttackController {
|
|||||||
// find the potential damage ratio the AI can cause
|
// find the potential damage ratio the AI can cause
|
||||||
double humanLifeToDamageRatio = 1000000;
|
double humanLifeToDamageRatio = 1000000;
|
||||||
if (candidateUnblockedDamage > 0) {
|
if (candidateUnblockedDamage > 0) {
|
||||||
humanLifeToDamageRatio = (double) (opp.getLife() - ComputerUtil.possibleNonCombatDamage(ai)) / candidateUnblockedDamage;
|
humanLifeToDamageRatio = (double) (opp.getLife() - ComputerUtil.possibleNonCombatDamage(ai, opp)) / candidateUnblockedDamage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine if the ai outnumbers the player
|
// determine if the ai outnumbers the player
|
||||||
@@ -936,12 +930,9 @@ public class AiAttackController {
|
|||||||
|
|
||||||
// *********************
|
// *********************
|
||||||
// if outnumber and superior ratio work out whether attritional all out
|
// if outnumber and superior ratio work out whether attritional all out
|
||||||
// attacking will work
|
// attacking will work attritional attack will expect some creatures to die but to achieve
|
||||||
// attritional attack will expect some creatures to die but to achieve
|
// victory by sheer weight of numbers attacking turn after turn. It's not calculate very
|
||||||
// victory by sheer weight
|
// carefully, the accuracy can probably be improved
|
||||||
// of numbers attacking turn after turn. It's not calculate very
|
|
||||||
// carefully, the accuracy
|
|
||||||
// can probably be improved
|
|
||||||
// *********************
|
// *********************
|
||||||
boolean doAttritionalAttack = false;
|
boolean doAttritionalAttack = false;
|
||||||
// get list of attackers ordered from low power to high
|
// get list of attackers ordered from low power to high
|
||||||
@@ -975,7 +966,6 @@ public class AiAttackController {
|
|||||||
doAttritionalAttack = true;
|
doAttritionalAttack = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// System.out.println(doAttritionalAttack + " = do attritional attack");
|
|
||||||
// *********************
|
// *********************
|
||||||
// end attritional attack calculation
|
// end attritional attack calculation
|
||||||
// *********************
|
// *********************
|
||||||
@@ -1025,11 +1015,9 @@ public class AiAttackController {
|
|||||||
// end see how long until unblockable attackers will be fatal
|
// end see how long until unblockable attackers will be fatal
|
||||||
// *****************
|
// *****************
|
||||||
|
|
||||||
|
|
||||||
// decide on attack aggression based on a comparison of forces, life
|
// decide on attack aggression based on a comparison of forces, life
|
||||||
// totals and other considerations
|
// totals and other considerations some bad "magic numbers" here
|
||||||
// some bad "magic numbers" here, TODO replace with nice descriptive
|
// TODO replace with nice descriptive variable names
|
||||||
// variable names
|
|
||||||
if (ratioDiff > 0 && doAttritionalAttack) {
|
if (ratioDiff > 0 && doAttritionalAttack) {
|
||||||
this.aiAggression = 5; // attack at all costs
|
this.aiAggression = 5; // attack at all costs
|
||||||
} else if ((ratioDiff >= 1 && this.attackers.size() > 1 && (humanLifeToDamageRatio < 2 || outNumber > 0))
|
} else if ((ratioDiff >= 1 && this.attackers.size() > 1 && (humanLifeToDamageRatio < 2 || outNumber > 0))
|
||||||
@@ -1105,17 +1093,18 @@ public class AiAttackController {
|
|||||||
// if enough damage: switch to next planeswalker or player
|
// if enough damage: switch to next planeswalker or player
|
||||||
if (damage >= pw.getCounters(CounterEnumType.LOYALTY)) {
|
if (damage >= pw.getCounters(CounterEnumType.LOYALTY)) {
|
||||||
List<Card> pwDefending = combat.getDefendingPlaneswalkers();
|
List<Card> pwDefending = combat.getDefendingPlaneswalkers();
|
||||||
boolean found = false;
|
|
||||||
// look for next planeswalker
|
// look for next planeswalker
|
||||||
for (Card walker : pwDefending) {
|
for (Card walker : Lists.newArrayList(pwDefending)) {
|
||||||
if (combat.getAttackersOf(walker).isEmpty()) {
|
if (!combat.getAttackersOf(walker).isEmpty()) {
|
||||||
defender = walker;
|
pwDefending.remove(walker);
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (pwDefending.isEmpty()) {
|
||||||
defender = combat.getDefendingPlayers().get(0);
|
defender = Collections.min(Lists.newArrayList(combat.getDefendingPlayers()), PlayerPredicates.compareByLife());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending);
|
||||||
|
defender = pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1224,9 +1213,8 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// look at the attacker in relation to the blockers to establish a
|
// look at the attacker in relation to the blockers to establish a
|
||||||
// number of factors about the attacking
|
// number of factors about the attacking context that will be relevant
|
||||||
// context that will be relevant to the attackers decision according to
|
// to the attackers decision according to the selected strategy
|
||||||
// the selected strategy
|
|
||||||
for (final Card defender : validBlockers) {
|
for (final Card defender : validBlockers) {
|
||||||
// if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check
|
// if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check
|
||||||
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
|
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
|
||||||
@@ -1300,14 +1288,11 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (numberOfPossibleBlockers > 2
|
if (numberOfPossibleBlockers > 2
|
||||||
|| (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, combat))
|
|| (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, this.defendingOpponent))
|
||||||
|| (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, combat))) {
|
|| (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, this.defendingOpponent))) {
|
||||||
canBeBlocked = true;
|
canBeBlocked = true;
|
||||||
}
|
}
|
||||||
/*System.out.println(attacker + " canBeKilledByOne: " + canBeKilledByOne + " canKillAll: "
|
// decide if the creature should attack based on the prevailing strategy choice in aiAggression
|
||||||
+ canKillAll + " isWorthLessThanAllKillers: " + isWorthLessThanAllKillers + " canBeBlocked: " + canBeBlocked);*/
|
|
||||||
// decide if the creature should attack based on the prevailing strategy
|
|
||||||
// choice in aiAggression
|
|
||||||
switch (this.aiAggression) {
|
switch (this.aiAggression) {
|
||||||
case 6: // Exalted: expecting to at least kill a creature of equal value or not be blocked
|
case 6: // Exalted: expecting to at least kill a creature of equal value or not be blocked
|
||||||
if ((canKillAll && isWorthLessThanAllKillers) || !canBeBlocked) {
|
if ((canKillAll && isWorthLessThanAllKillers) || !canBeBlocked) {
|
||||||
@@ -1373,8 +1358,7 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if card has a Exert Trigger which would target,
|
// if card has a Exert Trigger which would target,
|
||||||
// but there are no creatures it can target, no need to exert with
|
// but there are no creatures it can target, no need to exert with it
|
||||||
// it
|
|
||||||
boolean missTarget = false;
|
boolean missTarget = false;
|
||||||
for (Trigger t : c.getTriggers()) {
|
for (Trigger t : c.getTriggers()) {
|
||||||
if (!TriggerType.Exerted.equals(t.getMode())) {
|
if (!TriggerType.Exerted.equals(t.getMode())) {
|
||||||
@@ -1563,4 +1547,4 @@ public class AiAttackController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // end class ComputerUtil_Attack2
|
}
|
||||||
|
|||||||
@@ -180,11 +180,9 @@ public class AiBlockController {
|
|||||||
|
|
||||||
// Good Blocks means a good trade or no trade
|
// Good Blocks means a good trade or no trade
|
||||||
private void makeGoodBlocks(final Combat combat) {
|
private void makeGoodBlocks(final Combat combat) {
|
||||||
|
|
||||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||||
|
|
||||||
for (final Card attacker : attackersLeft) {
|
for (final Card attacker : attackersLeft) {
|
||||||
|
|
||||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|
||||||
|| attacker.hasKeyword(Keyword.MENACE)) {
|
|| attacker.hasKeyword(Keyword.MENACE)) {
|
||||||
@@ -192,7 +190,6 @@ public class AiBlockController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Card blocker = null;
|
Card blocker = null;
|
||||||
|
|
||||||
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||||
|
|
||||||
final List<Card> safeBlockers = getSafeBlockers(combat, attacker, blockers);
|
final List<Card> safeBlockers = getSafeBlockers(combat, attacker, blockers);
|
||||||
@@ -305,7 +302,6 @@ public class AiBlockController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Card blocker = null;
|
Card blocker = null;
|
||||||
|
|
||||||
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||||
|
|
||||||
for (Card b : blockers) {
|
for (Card b : blockers) {
|
||||||
@@ -366,10 +362,9 @@ public class AiBlockController {
|
|||||||
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
||||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
||||||
// if the total damage of the blockgang was not enough
|
// if the total damage of the blockgang was not enough
|
||||||
// without but is enough with this blocker finish the
|
// without but is enough with this blocker finish the blockgang
|
||||||
// blockgang
|
|
||||||
if (ComputerUtilCombat.totalFirstStrikeDamageOfBlockers(attacker, blockGang) < damageNeeded
|
if (ComputerUtilCombat.totalFirstStrikeDamageOfBlockers(attacker, blockGang) < damageNeeded
|
||||||
|| CombatUtil.needsBlockers(attacker) > blockGang.size()) {
|
|| CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > blockGang.size()) {
|
||||||
blockGang.add(blocker);
|
blockGang.add(blocker);
|
||||||
if (ComputerUtilCombat.totalFirstStrikeDamageOfBlockers(attacker, blockGang) >= damageNeeded) {
|
if (ComputerUtilCombat.totalFirstStrikeDamageOfBlockers(attacker, blockGang) >= damageNeeded) {
|
||||||
currentAttackers.remove(attacker);
|
currentAttackers.remove(attacker);
|
||||||
@@ -407,7 +402,7 @@ public class AiBlockController {
|
|||||||
boolean foundDoubleBlock = false; // if true, a good double block is found
|
boolean foundDoubleBlock = false; // if true, a good double block is found
|
||||||
|
|
||||||
// AI can't handle good blocks with more than three creatures yet
|
// AI can't handle good blocks with more than three creatures yet
|
||||||
if (CombatUtil.needsBlockers(attacker) > (considerTripleBlock ? 3 : 2)) {
|
if (CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > (considerTripleBlock ? 3 : 2)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,7 +439,7 @@ public class AiBlockController {
|
|||||||
final int addedValue = ComputerUtilCard.evaluateCreature(blocker);
|
final int addedValue = ComputerUtilCard.evaluateCreature(blocker);
|
||||||
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
||||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
||||||
if ((damageNeeded > currentDamage || CombatUtil.needsBlockers(attacker) > blockGang.size())
|
if ((damageNeeded > currentDamage || CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > blockGang.size())
|
||||||
&& !(damageNeeded > currentDamage + additionalDamage)
|
&& !(damageNeeded > currentDamage + additionalDamage)
|
||||||
// The attacker will be killed
|
// The attacker will be killed
|
||||||
&& (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage()
|
&& (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage()
|
||||||
@@ -454,8 +449,7 @@ public class AiBlockController {
|
|||||||
|| (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)))
|
|| (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)))
|
||||||
// or life is in danger
|
// or life is in danger
|
||||||
&& CombatUtil.canBlock(attacker, blocker, combat)) {
|
&& CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||||
// this is needed for attackers that can't be blocked by
|
// this is needed for attackers that can't be blocked by more than 1
|
||||||
// more than 1
|
|
||||||
currentAttackers.remove(attacker);
|
currentAttackers.remove(attacker);
|
||||||
combat.addBlocker(attacker, blocker);
|
combat.addBlocker(attacker, blocker);
|
||||||
if (CombatUtil.canBlock(attacker, leader, combat)) {
|
if (CombatUtil.canBlock(attacker, leader, combat)) {
|
||||||
@@ -496,7 +490,7 @@ public class AiBlockController {
|
|||||||
final int addedValue3 = ComputerUtilCard.evaluateCreature(secondBlocker);
|
final int addedValue3 = ComputerUtilCard.evaluateCreature(secondBlocker);
|
||||||
final int netCombatDamage = attacker.getNetCombatDamage();
|
final int netCombatDamage = attacker.getNetCombatDamage();
|
||||||
|
|
||||||
if ((damageNeeded > currentDamage || CombatUtil.needsBlockers(attacker) > blockGang.size())
|
if ((damageNeeded > currentDamage || CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > blockGang.size())
|
||||||
&& !(damageNeeded > currentDamage + additionalDamage2 + additionalDamage3)
|
&& !(damageNeeded > currentDamage + additionalDamage2 + additionalDamage3)
|
||||||
// The attacker will be killed
|
// The attacker will be killed
|
||||||
&& ((absorbedDamage2 + absorbedDamage > netCombatDamage && absorbedDamage3 + absorbedDamage > netCombatDamage
|
&& ((absorbedDamage2 + absorbedDamage > netCombatDamage && absorbedDamage3 + absorbedDamage > netCombatDamage
|
||||||
@@ -510,8 +504,7 @@ public class AiBlockController {
|
|||||||
// or life is in danger
|
// or life is in danger
|
||||||
&& CombatUtil.canBlock(attacker, secondBlocker, combat)
|
&& CombatUtil.canBlock(attacker, secondBlocker, combat)
|
||||||
&& CombatUtil.canBlock(attacker, thirdBlocker, combat)) {
|
&& CombatUtil.canBlock(attacker, thirdBlocker, combat)) {
|
||||||
// this is needed for attackers that can't be blocked by
|
// this is needed for attackers that can't be blocked by more than 1
|
||||||
// more than 1
|
|
||||||
currentAttackers.remove(attacker);
|
currentAttackers.remove(attacker);
|
||||||
combat.addBlocker(attacker, thirdBlocker);
|
combat.addBlocker(attacker, thirdBlocker);
|
||||||
if (CombatUtil.canBlock(attacker, secondBlocker, combat)) {
|
if (CombatUtil.canBlock(attacker, secondBlocker, combat)) {
|
||||||
@@ -587,12 +580,10 @@ public class AiBlockController {
|
|||||||
* @param combat a {@link forge.game.combat.Combat} object.
|
* @param combat a {@link forge.game.combat.Combat} object.
|
||||||
*/
|
*/
|
||||||
private void makeTradeBlocks(final Combat combat) {
|
private void makeTradeBlocks(final Combat combat) {
|
||||||
|
|
||||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||||
List<Card> killingBlockers;
|
List<Card> killingBlockers;
|
||||||
|
|
||||||
for (final Card attacker : attackersLeft) {
|
for (final Card attacker : attackersLeft) {
|
||||||
|
|
||||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||||
|| attacker.hasKeyword(Keyword.MENACE)
|
|| attacker.hasKeyword(Keyword.MENACE)
|
||||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||||
@@ -628,7 +619,6 @@ public class AiBlockController {
|
|||||||
|
|
||||||
// Chump Blocks (should only be made if life is in danger)
|
// Chump Blocks (should only be made if life is in danger)
|
||||||
private void makeChumpBlocks(final Combat combat) {
|
private void makeChumpBlocks(final Combat combat) {
|
||||||
|
|
||||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||||
|
|
||||||
makeChumpBlocks(combat, currentAttackers);
|
makeChumpBlocks(combat, currentAttackers);
|
||||||
@@ -639,7 +629,6 @@ public class AiBlockController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void makeChumpBlocks(final Combat combat, List<Card> attackers) {
|
private void makeChumpBlocks(final Combat combat, List<Card> attackers) {
|
||||||
|
|
||||||
if (attackers.isEmpty() || !ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (attackers.isEmpty() || !ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -694,11 +683,9 @@ public class AiBlockController {
|
|||||||
|
|
||||||
// Block creatures with "can't be blocked except by two or more creatures"
|
// Block creatures with "can't be blocked except by two or more creatures"
|
||||||
private void makeMultiChumpBlocks(final Combat combat) {
|
private void makeMultiChumpBlocks(final Combat combat) {
|
||||||
|
|
||||||
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
List<Card> currentAttackers = new ArrayList<>(attackersLeft);
|
||||||
|
|
||||||
for (final Card attacker : currentAttackers) {
|
for (final Card attacker : currentAttackers) {
|
||||||
|
|
||||||
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||||
&& !attacker.hasKeyword(Keyword.MENACE)
|
&& !attacker.hasKeyword(Keyword.MENACE)
|
||||||
&& !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
&& !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||||
@@ -730,14 +717,12 @@ public class AiBlockController {
|
|||||||
|
|
||||||
/** Reinforce blockers blocking attackers with trample (should only be made if life is in danger) */
|
/** Reinforce blockers blocking attackers with trample (should only be made if life is in danger) */
|
||||||
private void reinforceBlockersAgainstTrample(final Combat combat) {
|
private void reinforceBlockersAgainstTrample(final Combat combat) {
|
||||||
|
|
||||||
List<Card> chumpBlockers;
|
List<Card> chumpBlockers;
|
||||||
|
|
||||||
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE);
|
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE);
|
||||||
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
|
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
|
||||||
|
|
||||||
// TODO - should check here for a "rampage-like" trigger that replaced
|
// TODO - should check here for a "rampage-like" trigger that replaced the keyword:
|
||||||
// the keyword:
|
|
||||||
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
|
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
|
||||||
|
|
||||||
for (final Card attacker : tramplingAttackers) {
|
for (final Card attacker : tramplingAttackers) {
|
||||||
@@ -764,7 +749,6 @@ public class AiBlockController {
|
|||||||
|
|
||||||
/** Support blockers not destroying the attacker with more blockers to try to kill the attacker */
|
/** Support blockers not destroying the attacker with more blockers to try to kill the attacker */
|
||||||
private void reinforceBlockersToKill(final Combat combat) {
|
private void reinforceBlockersToKill(final Combat combat) {
|
||||||
|
|
||||||
List<Card> safeBlockers;
|
List<Card> safeBlockers;
|
||||||
List<Card> blockers;
|
List<Card> blockers;
|
||||||
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock));
|
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock));
|
||||||
@@ -933,7 +917,6 @@ public class AiBlockController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void clearBlockers(final Combat combat, final List<Card> possibleBlockers) {
|
private void clearBlockers(final Combat combat, final List<Card> possibleBlockers) {
|
||||||
|
|
||||||
final List<Card> oldBlockers = combat.getAllBlockers();
|
final List<Card> oldBlockers = combat.getAllBlockers();
|
||||||
for (final Card blocker : oldBlockers) {
|
for (final Card blocker : oldBlockers) {
|
||||||
if (blocker.getController() == ai) // don't touch other player's blockers
|
if (blocker.getController() == ai) // don't touch other player's blockers
|
||||||
@@ -1036,27 +1019,21 @@ public class AiBlockController {
|
|||||||
} else {
|
} else {
|
||||||
lifeInDanger = false;
|
lifeInDanger = false;
|
||||||
}
|
}
|
||||||
// if life is still in danger
|
// Reinforce blockers blocking attackers with trample if life is still in danger
|
||||||
// Reinforce blockers blocking attackers with trample if life is
|
|
||||||
// still
|
|
||||||
// in danger
|
|
||||||
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
reinforceBlockersAgainstTrample(combat);
|
reinforceBlockersAgainstTrample(combat);
|
||||||
} else {
|
} else {
|
||||||
lifeInDanger = false;
|
lifeInDanger = false;
|
||||||
}
|
}
|
||||||
// Support blockers not destroying the attacker with more blockers
|
// Support blockers not destroying the attacker with more blockers
|
||||||
// to
|
// to try to kill the attacker
|
||||||
// try to kill the attacker
|
|
||||||
if (!lifeInDanger) {
|
if (!lifeInDanger) {
|
||||||
reinforceBlockersToKill(combat);
|
reinforceBlockersToKill(combat);
|
||||||
}
|
}
|
||||||
|
|
||||||
// == 2. If the AI life would still be in danger make a safer
|
// == 2. If the AI life would still be in danger make a safer approach ==
|
||||||
// approach ==
|
|
||||||
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
clearBlockers(combat, possibleBlockers); // reset every block
|
clearBlockers(combat, possibleBlockers); // reset every block assignment
|
||||||
// assignment
|
|
||||||
makeTradeBlocks(combat); // choose necessary trade blocks
|
makeTradeBlocks(combat); // choose necessary trade blocks
|
||||||
// if life is in danger
|
// if life is in danger
|
||||||
makeGoodBlocks(combat);
|
makeGoodBlocks(combat);
|
||||||
@@ -1066,8 +1043,7 @@ public class AiBlockController {
|
|||||||
} else {
|
} else {
|
||||||
lifeInDanger = false;
|
lifeInDanger = false;
|
||||||
}
|
}
|
||||||
// Reinforce blockers blocking attackers with trample if life is
|
// Reinforce blockers blocking attackers with trample if life is still in danger
|
||||||
// still in danger
|
|
||||||
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
reinforceBlockersAgainstTrample(combat);
|
reinforceBlockersAgainstTrample(combat);
|
||||||
} else {
|
} else {
|
||||||
@@ -1077,11 +1053,9 @@ public class AiBlockController {
|
|||||||
reinforceBlockersToKill(combat);
|
reinforceBlockersToKill(combat);
|
||||||
}
|
}
|
||||||
|
|
||||||
// == 3. If the AI life would be in serious danger make an even
|
// == 3. If the AI life would be in serious danger make an even safer approach ==
|
||||||
// safer approach ==
|
|
||||||
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
||||||
clearBlockers(combat, possibleBlockers); // reset every block
|
clearBlockers(combat, possibleBlockers); // reset every block assignment
|
||||||
// assignment
|
|
||||||
makeChumpBlocks(combat); // choose chump blocks
|
makeChumpBlocks(combat); // choose chump blocks
|
||||||
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
makeTradeBlocks(combat); // choose necessary trade
|
makeTradeBlocks(combat); // choose necessary trade
|
||||||
@@ -1090,15 +1064,13 @@ public class AiBlockController {
|
|||||||
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
makeGoodBlocks(combat);
|
makeGoodBlocks(combat);
|
||||||
}
|
}
|
||||||
// Reinforce blockers blocking attackers with trample if life is
|
// Reinforce blockers blocking attackers with trample if life is still in danger
|
||||||
// still in danger
|
|
||||||
else {
|
else {
|
||||||
reinforceBlockersAgainstTrample(combat);
|
reinforceBlockersAgainstTrample(combat);
|
||||||
}
|
}
|
||||||
makeGangBlocks(combat);
|
makeGangBlocks(combat);
|
||||||
// Support blockers not destroying the attacker with more
|
// Support blockers not destroying the attacker with more
|
||||||
// blockers
|
// blockers to try to kill the attacker
|
||||||
// to try to kill the attacker
|
|
||||||
reinforceBlockersToKill(combat);
|
reinforceBlockersToKill(combat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1108,7 +1080,7 @@ public class AiBlockController {
|
|||||||
chumpBlockers.addAll(CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."));
|
chumpBlockers.addAll(CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."));
|
||||||
// if an attacker with lure attacks - all that can block
|
// if an attacker with lure attacks - all that can block
|
||||||
for (final Card blocker : blockersLeft) {
|
for (final Card blocker : blockersLeft) {
|
||||||
if (CombatUtil.mustBlockAnAttacker(blocker, combat)) {
|
if (CombatUtil.mustBlockAnAttacker(blocker, combat, null)) {
|
||||||
chumpBlockers.add(blocker);
|
chumpBlockers.add(blocker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1118,7 +1090,7 @@ public class AiBlockController {
|
|||||||
blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
|
blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
|
||||||
for (final Card blocker : blockers) {
|
for (final Card blocker : blockers) {
|
||||||
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
|
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
|
||||||
&& (CombatUtil.mustBlockAnAttacker(blocker, combat)
|
&& (CombatUtil.mustBlockAnAttacker(blocker, combat, null)
|
||||||
|| blocker.hasKeyword("CARDNAME blocks each turn if able.")
|
|| blocker.hasKeyword("CARDNAME blocks each turn if able.")
|
||||||
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
|
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
|
||||||
combat.addBlocker(attacker, blocker);
|
combat.addBlocker(attacker, blocker);
|
||||||
@@ -1137,7 +1109,6 @@ public class AiBlockController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// check to see if it's possible to defend a Planeswalker under attack with a chump block,
|
// check to see if it's possible to defend a Planeswalker under attack with a chump block,
|
||||||
// unless life is low enough to be more worried about saving preserving the life total
|
// unless life is low enough to be more worried about saving preserving the life total
|
||||||
if (ai.getController().isAI() && !ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
if (ai.getController().isAI() && !ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ import com.google.common.collect.Lists;
|
|||||||
|
|
||||||
import forge.ai.ability.ChangeZoneAi;
|
import forge.ai.ability.ChangeZoneAi;
|
||||||
import forge.ai.ability.ExploreAi;
|
import forge.ai.ability.ExploreAi;
|
||||||
|
import forge.ai.ability.LearnAi;
|
||||||
import forge.ai.simulation.SpellAbilityPicker;
|
import forge.ai.simulation.SpellAbilityPicker;
|
||||||
|
import forge.card.CardStateName;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
import forge.deck.CardPool;
|
import forge.deck.CardPool;
|
||||||
@@ -45,6 +47,7 @@ import forge.game.Game;
|
|||||||
import forge.game.GameActionUtil;
|
import forge.game.GameActionUtil;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.GlobalRuleChange;
|
import forge.game.GlobalRuleChange;
|
||||||
|
import forge.game.ability.AbilityKey;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.ability.SpellApiBased;
|
import forge.game.ability.SpellApiBased;
|
||||||
@@ -77,6 +80,7 @@ import forge.game.player.Player;
|
|||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.replacement.ReplaceMoved;
|
import forge.game.replacement.ReplaceMoved;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
|
import forge.game.replacement.ReplacementLayer;
|
||||||
import forge.game.replacement.ReplacementType;
|
import forge.game.replacement.ReplacementType;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.LandAbility;
|
import forge.game.spellability.LandAbility;
|
||||||
@@ -88,6 +92,7 @@ import forge.game.spellability.SpellAbilityCondition;
|
|||||||
import forge.game.spellability.SpellAbilityPredicates;
|
import forge.game.spellability.SpellAbilityPredicates;
|
||||||
import forge.game.spellability.SpellPermanent;
|
import forge.game.spellability.SpellPermanent;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.trigger.WrappedAbility;
|
import forge.game.trigger.WrappedAbility;
|
||||||
@@ -249,6 +254,9 @@ public class AiController {
|
|||||||
boolean rightapi = false;
|
boolean rightapi = false;
|
||||||
Player activatingPlayer = sa.getActivatingPlayer();
|
Player activatingPlayer = sa.getActivatingPlayer();
|
||||||
|
|
||||||
|
// for xPaid stuff
|
||||||
|
card.setCastSA(sa);
|
||||||
|
|
||||||
// Trigger play improvements
|
// Trigger play improvements
|
||||||
for (final Trigger tr : card.getTriggers()) {
|
for (final Trigger tr : card.getTriggers()) {
|
||||||
// These triggers all care for ETB effects
|
// These triggers all care for ETB effects
|
||||||
@@ -305,6 +313,8 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exSA.setTrigger(tr);
|
exSA.setTrigger(tr);
|
||||||
|
// need to set TriggeredObject
|
||||||
|
exSA.setTriggeringObject(AbilityKey.Card, card);
|
||||||
|
|
||||||
// for trigger test, need to ignore the conditions
|
// for trigger test, need to ignore the conditions
|
||||||
SpellAbilityCondition cons = exSA.getConditions();
|
SpellAbilityCondition cons = exSA.getConditions();
|
||||||
@@ -379,7 +389,7 @@ public class AiController {
|
|||||||
final List<SpellAbility> spellAbility = Lists.newArrayList();
|
final List<SpellAbility> spellAbility = Lists.newArrayList();
|
||||||
for (final Card c : l) {
|
for (final Card c : l) {
|
||||||
for (final SpellAbility sa : c.getNonManaAbilities()) {
|
for (final SpellAbility sa : c.getNonManaAbilities()) {
|
||||||
// Check if this AF is a Counterpsell
|
// Check if this AF is a Counterspell
|
||||||
if (sa.getApi() == ApiType.Counter) {
|
if (sa.getApi() == ApiType.Counter) {
|
||||||
spellAbility.add(sa);
|
spellAbility.add(sa);
|
||||||
}
|
}
|
||||||
@@ -430,17 +440,22 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't play the land if it has cycling and enough lands are available
|
|
||||||
final FCollectionView<SpellAbility> spellAbilities = c.getSpellAbilities();
|
|
||||||
|
|
||||||
final CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
|
final CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
|
||||||
CardCollection lands = new CardCollection(battlefield);
|
CardCollection lands = new CardCollection(battlefield);
|
||||||
lands.addAll(hand);
|
lands.addAll(hand);
|
||||||
lands = CardLists.filter(lands, CardPredicates.Presets.LANDS);
|
lands = CardLists.filter(lands, CardPredicates.Presets.LANDS);
|
||||||
int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc);
|
int maxCmcInHand = Aggregates.max(hand, CardPredicates.Accessors.fnGetCmc);
|
||||||
|
|
||||||
|
if (lands.size() >= Math.max(maxCmcInHand, 6)) {
|
||||||
|
// don't play MDFC land if other side is spell and enough lands are available
|
||||||
|
if (!c.isLand() || (c.isModal() && !c.getState(CardStateName.Modal).getType().isLand())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't play the land if it has cycling and enough lands are available
|
||||||
|
final FCollectionView<SpellAbility> spellAbilities = c.getSpellAbilities();
|
||||||
for (final SpellAbility sa : spellAbilities) {
|
for (final SpellAbility sa : spellAbilities) {
|
||||||
if (sa.isCycling()) {
|
if (sa.isCycling()) {
|
||||||
if (lands.size() >= Math.max(maxCmcInHand, 6)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -498,23 +513,38 @@ public class AiController {
|
|||||||
landList = unreflectedLands;
|
landList = unreflectedLands;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//try to skip lands that enter the battlefield tapped
|
//try to skip lands that enter the battlefield tapped
|
||||||
if (!nonLandsInHand.isEmpty()) {
|
if (!nonLandsInHand.isEmpty()) {
|
||||||
CardCollection nonTappeddLands = new CardCollection();
|
CardCollection nonTappedLands = new CardCollection();
|
||||||
for (Card land : landList) {
|
for (Card land : landList) {
|
||||||
// Is this the best way to check if a land ETB Tapped?
|
// check replacement effects if land would enter tapped or not
|
||||||
if (land.hasSVar("ETBTappedSVar")) {
|
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(land);
|
||||||
|
repParams.put(AbilityKey.Origin, land.getZone().getZoneType());
|
||||||
|
repParams.put(AbilityKey.Destination, ZoneType.Battlefield);
|
||||||
|
repParams.put(AbilityKey.Source, land);
|
||||||
|
|
||||||
|
boolean foundTapped = false;
|
||||||
|
for (ReplacementEffect re : player.getGame().getReplacementHandler().getReplacementList(ReplacementType.Moved, repParams, ReplacementLayer.Other)) {
|
||||||
|
SpellAbility reSA = re.ensureAbility();
|
||||||
|
if (reSA == null || !ApiType.Tap.equals(reSA.getApi())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Glacial Fortress and friends
|
reSA.setActivatingPlayer(reSA.getHostCard().getController());
|
||||||
if (land.hasSVar("ETBCheckSVar") && CardFactoryUtil.xCount(land, land.getSVar("ETBCheckSVar")) == 0) {
|
if (reSA.metConditions()) {
|
||||||
|
foundTapped = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO if this is the only source for a color we need badly prioritize it instead
|
||||||
|
if (foundTapped) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
nonTappeddLands.add(land);
|
|
||||||
|
nonTappedLands.add(land);
|
||||||
}
|
}
|
||||||
if (!nonTappeddLands.isEmpty()) {
|
if (!nonTappedLands.isEmpty()) {
|
||||||
landList = nonTappeddLands;
|
landList = nonTappedLands;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,7 +616,6 @@ public class AiController {
|
|||||||
sa.setActivatingPlayer(player);
|
sa.setActivatingPlayer(player);
|
||||||
// check everything necessary
|
// check everything necessary
|
||||||
|
|
||||||
|
|
||||||
AiPlayDecision opinion = canPlayAndPayFor(currentSA);
|
AiPlayDecision opinion = canPlayAndPayFor(currentSA);
|
||||||
//PhaseHandler ph = game.getPhaseHandler();
|
//PhaseHandler ph = game.getPhaseHandler();
|
||||||
// System.out.printf("Ai thinks '%s' of %s @ %s %s >>> \n", opinion, sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase());
|
// System.out.printf("Ai thinks '%s' of %s @ %s %s >>> \n", opinion, sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase());
|
||||||
@@ -717,14 +746,22 @@ public class AiController {
|
|||||||
return AiPlayDecision.CantPlaySa;
|
return AiPlayDecision.CantPlaySa;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean xCost = sa.getPayCosts().hasXInAnyCostPart();
|
boolean xCost = sa.getPayCosts().hasXInAnyCostPart() || sa.getHostCard().hasStartOfKeyword("Strive");
|
||||||
if (!xCost && !ComputerUtilCost.canPayCost(sa, player)) {
|
if (!xCost && !ComputerUtilCost.canPayCost(sa, player)) {
|
||||||
// for most costs, it's OK to check if they can be paid early in order to avoid running a heavy API check
|
// for most costs, it's OK to check if they can be paid early in order to avoid running a heavy API check
|
||||||
// when the AI won't even be able to play the spell in the first place (even if it could afford it)
|
// when the AI won't even be able to play the spell in the first place (even if it could afford it)
|
||||||
return AiPlayDecision.CantAfford;
|
return AiPlayDecision.CantAfford;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// state needs to be switched here so API checks evaluate the right face
|
||||||
|
if (sa.getCardState() != null && !sa.getHostCard().isInPlay() && sa.getCardState().getStateName() == CardStateName.Modal) {
|
||||||
|
sa.getHostCard().setState(CardStateName.Modal, false);
|
||||||
|
}
|
||||||
AiPlayDecision canPlay = canPlaySa(sa); // this is the "heaviest" check, which also sets up targets, defines X, etc.
|
AiPlayDecision canPlay = canPlaySa(sa); // this is the "heaviest" check, which also sets up targets, defines X, etc.
|
||||||
|
if (sa.getCardState() != null && !sa.getHostCard().isInPlay() && sa.getCardState().getStateName() == CardStateName.Modal) {
|
||||||
|
sa.getHostCard().setState(CardStateName.Original, false);
|
||||||
|
}
|
||||||
|
|
||||||
if (canPlay != AiPlayDecision.WillPlay) {
|
if (canPlay != AiPlayDecision.WillPlay) {
|
||||||
return canPlay;
|
return canPlay;
|
||||||
}
|
}
|
||||||
@@ -784,8 +821,7 @@ public class AiController {
|
|||||||
if (!canPlay) {
|
if (!canPlay) {
|
||||||
return AiPlayDecision.CantPlayAi;
|
return AiPlayDecision.CantPlayAi;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
Cost payCosts = sa.getPayCosts();
|
Cost payCosts = sa.getPayCosts();
|
||||||
if (payCosts != null) {
|
if (payCosts != null) {
|
||||||
ManaCost mana = payCosts.getTotalMana();
|
ManaCost mana = payCosts.getTotalMana();
|
||||||
@@ -826,8 +862,11 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
|
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
|
||||||
}
|
}
|
||||||
if (sa.usesTargeting() && !sa.isTargetNumberValid()) {
|
if (sa.usesTargeting()) {
|
||||||
if (!sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
if (!sa.isTargetNumberValid() && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||||
|
return AiPlayDecision.TargetingFailed;
|
||||||
|
}
|
||||||
|
if (!StaticAbilityMustTarget.meetsMustTargetRestriction(sa)) {
|
||||||
return AiPlayDecision.TargetingFailed;
|
return AiPlayDecision.TargetingFailed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -850,7 +889,7 @@ public class AiController {
|
|||||||
public boolean isNonDisabledCardInPlay(final String cardName) {
|
public boolean isNonDisabledCardInPlay(final String cardName) {
|
||||||
for (Card card : player.getCardsIn(ZoneType.Battlefield)) {
|
for (Card card : player.getCardsIn(ZoneType.Battlefield)) {
|
||||||
if (card.getName().equals(cardName)) {
|
if (card.getName().equals(cardName)) {
|
||||||
// TODO - Better logic to detemine if a permanent is disabled by local effects
|
// TODO - Better logic to determine if a permanent is disabled by local effects
|
||||||
// currently assuming any permanent enchanted by another player
|
// currently assuming any permanent enchanted by another player
|
||||||
// is disabled and a second copy is necessary
|
// is disabled and a second copy is necessary
|
||||||
// will need actual logic that determines if the enchantment is able
|
// will need actual logic that determines if the enchantment is able
|
||||||
@@ -1110,12 +1149,18 @@ public class AiController {
|
|||||||
final CardCollection discardList = new CardCollection();
|
final CardCollection discardList = new CardCollection();
|
||||||
int count = 0;
|
int count = 0;
|
||||||
if (sa != null) {
|
if (sa != null) {
|
||||||
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
sourceCard = sa.getHostCard();
|
sourceCard = sa.getHostCard();
|
||||||
if ("Always".equals(sa.getParam("AILogic")) && !validCards.isEmpty()) {
|
if ("Always".equals(logic) && !validCards.isEmpty()) {
|
||||||
min = 1;
|
min = 1;
|
||||||
} else if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) {
|
} else if (logic.startsWith("UnlessAtLife.")) {
|
||||||
|
int threshold = AbilityUtils.calculateAmount(sourceCard, logic.substring(logic.indexOf(".") + 1), sa);
|
||||||
|
if (player.getLife() <= threshold) {
|
||||||
|
min = 1;
|
||||||
|
}
|
||||||
|
} else if ("VolrathsShapeshifter".equals(logic)) {
|
||||||
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
|
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
|
||||||
} else if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
} else if ("DiscardCMCX".equals(logic)) {
|
||||||
final int cmc = sa.getXManaCostPaid();
|
final int cmc = sa.getXManaCostPaid();
|
||||||
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc));
|
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc));
|
||||||
if (discards.isEmpty()) {
|
if (discards.isEmpty()) {
|
||||||
@@ -1290,15 +1335,6 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) {
|
public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) {
|
||||||
if (logic.equalsIgnoreCase("ProtectFriendly")) {
|
|
||||||
final Player controller = hostCard.getController();
|
|
||||||
if (affected instanceof Player) {
|
|
||||||
return !((Player) affected).isOpponentOf(controller);
|
|
||||||
}
|
|
||||||
if (affected instanceof Card) {
|
|
||||||
return !((Card) affected).getController().isOpponentOf(controller);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1328,7 +1364,7 @@ public class AiController {
|
|||||||
|
|
||||||
public AiPlayDecision canPlayFromEffectAI(Spell spell, boolean mandatory, boolean withoutPayingManaCost) {
|
public AiPlayDecision canPlayFromEffectAI(Spell spell, boolean mandatory, boolean withoutPayingManaCost) {
|
||||||
int damage = ComputerUtil.getDamageForPlaying(player, spell);
|
int damage = ComputerUtil.getDamageForPlaying(player, spell);
|
||||||
if (damage >= player.getLife() && !player.cantLoseForZeroOrLessLife() && player.canLoseLife()) {
|
if (!mandatory && damage >= player.getLife() && !player.cantLoseForZeroOrLessLife() && player.canLoseLife()) {
|
||||||
return AiPlayDecision.CurseEffects;
|
return AiPlayDecision.CurseEffects;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1397,8 +1433,6 @@ public class AiController {
|
|||||||
private List<SpellAbility> singleSpellAbilityList(SpellAbility sa) {
|
private List<SpellAbility> singleSpellAbilityList(SpellAbility sa) {
|
||||||
if (sa == null) { return null; }
|
if (sa == null) { return null; }
|
||||||
|
|
||||||
// System.out.println("Chosen to play: " + sa);
|
|
||||||
|
|
||||||
final List<SpellAbility> abilities = Lists.newArrayList();
|
final List<SpellAbility> abilities = Lists.newArrayList();
|
||||||
abilities.add(sa);
|
abilities.add(sa);
|
||||||
return abilities;
|
return abilities;
|
||||||
@@ -1443,6 +1477,13 @@ public class AiController {
|
|||||||
if (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land)) {
|
if (!game.getPhaseHandler().is(PhaseType.MAIN1) || !isSafeToHoldLandDropForMain2(land)) {
|
||||||
final List<SpellAbility> abilities = Lists.newArrayList();
|
final List<SpellAbility> abilities = Lists.newArrayList();
|
||||||
|
|
||||||
|
// TODO extend this logic to evaluate MDFC with both sides land
|
||||||
|
// this can only happen if its a MDFC land
|
||||||
|
if (!land.isLand()) {
|
||||||
|
land.setState(CardStateName.Modal, true);
|
||||||
|
land.setBackSide(true);
|
||||||
|
}
|
||||||
|
|
||||||
LandAbility la = new LandAbility(land, player, null);
|
LandAbility la = new LandAbility(land, player, null);
|
||||||
la.setCardState(land.getCurrentState());
|
la.setCardState(land.getCurrentState());
|
||||||
if (la.canPlay()) {
|
if (la.canPlay()) {
|
||||||
@@ -1693,10 +1734,8 @@ public class AiController {
|
|||||||
for (int i = 0; i < numToExile; i++) {
|
for (int i = 0; i < numToExile; i++) {
|
||||||
Card chosen = null;
|
Card chosen = null;
|
||||||
for (final Card c : grave) { // Exile noncreatures first in
|
for (final Card c : grave) { // Exile noncreatures first in
|
||||||
// case we can revive. Might
|
// case we can revive. Might wanna do some additional
|
||||||
// wanna do some additional
|
// checking here for Flashback and the like.
|
||||||
// checking here for Flashback
|
|
||||||
// and the like.
|
|
||||||
if (!c.isCreature()) {
|
if (!c.isCreature()) {
|
||||||
chosen = c;
|
chosen = c;
|
||||||
break;
|
break;
|
||||||
@@ -1718,10 +1757,10 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean doTrigger(SpellAbility spell, boolean mandatory) {
|
public boolean doTrigger(SpellAbility spell, boolean mandatory) {
|
||||||
if (spell.getApi() != null)
|
|
||||||
return SpellApiToAi.Converter.get(spell.getApi()).doTriggerAI(player, spell, mandatory);
|
|
||||||
if (spell instanceof WrappedAbility)
|
if (spell instanceof WrappedAbility)
|
||||||
return doTrigger(((WrappedAbility)spell).getWrappedAbility(), mandatory);
|
return doTrigger(((WrappedAbility)spell).getWrappedAbility(), mandatory);
|
||||||
|
if (spell.getApi() != null)
|
||||||
|
return SpellApiToAi.Converter.get(spell.getApi()).doTriggerAI(player, spell, mandatory);
|
||||||
if (spell.getPayCosts() == Cost.Zero && spell.getTargetRestrictions() == null) {
|
if (spell.getPayCosts() == Cost.Zero && spell.getTargetRestrictions() == null) {
|
||||||
// For non-converted triggers (such as Cumulative Upkeep) that don't have costs or targets to worry about
|
// For non-converted triggers (such as Cumulative Upkeep) that don't have costs or targets to worry about
|
||||||
return true;
|
return true;
|
||||||
@@ -1735,12 +1774,21 @@ public class AiController {
|
|||||||
* @param sa the sa
|
* @param sa the sa
|
||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
public final boolean aiShouldRun(final ReplacementEffect effect, final SpellAbility sa) {
|
public final boolean aiShouldRun(final ReplacementEffect effect, final SpellAbility sa, GameEntity affected) {
|
||||||
Card hostCard = effect.getHostCard();
|
Card hostCard = effect.getHostCard();
|
||||||
if (hostCard.hasAlternateState()) {
|
if (hostCard.hasAlternateState()) {
|
||||||
hostCard = game.getCardState(hostCard);
|
hostCard = game.getCardState(hostCard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (effect.hasParam("AILogic") && effect.getParam("AILogic").equalsIgnoreCase("ProtectFriendly")) {
|
||||||
|
final Player controller = hostCard.getController();
|
||||||
|
if (affected instanceof Player) {
|
||||||
|
return !((Player) affected).isOpponentOf(controller);
|
||||||
|
}
|
||||||
|
if (affected instanceof Card) {
|
||||||
|
return !((Card) affected).getController().isOpponentOf(controller);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (effect.hasParam("AICheckSVar")) {
|
if (effect.hasParam("AICheckSVar")) {
|
||||||
System.out.println("aiShouldRun?" + sa);
|
System.out.println("aiShouldRun?" + sa);
|
||||||
final String svarToCheck = effect.getParam("AICheckSVar");
|
final String svarToCheck = effect.getParam("AICheckSVar");
|
||||||
@@ -1755,7 +1803,7 @@ public class AiController {
|
|||||||
compareTo = Integer.parseInt(strCmpTo);
|
compareTo = Integer.parseInt(strCmpTo);
|
||||||
} catch (final Exception ignored) {
|
} catch (final Exception ignored) {
|
||||||
if (sa == null) {
|
if (sa == null) {
|
||||||
compareTo = CardFactoryUtil.xCount(hostCard, hostCard.getSVar(strCmpTo));
|
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), effect);
|
||||||
} else {
|
} else {
|
||||||
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa);
|
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa);
|
||||||
}
|
}
|
||||||
@@ -1765,7 +1813,7 @@ public class AiController {
|
|||||||
int left = 0;
|
int left = 0;
|
||||||
|
|
||||||
if (sa == null) {
|
if (sa == null) {
|
||||||
left = CardFactoryUtil.xCount(hostCard, hostCard.getSVar(svarToCheck));
|
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, effect);
|
||||||
} else {
|
} else {
|
||||||
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
|
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
|
||||||
}
|
}
|
||||||
@@ -1855,11 +1903,13 @@ public class AiController {
|
|||||||
} else if ("LowestLoseLife".equals(logic)) {
|
} else if ("LowestLoseLife".equals(logic)) {
|
||||||
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
|
return MyRandom.getRandom().nextInt(Math.min(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
|
||||||
} else if ("HighestLoseLife".equals(logic)) {
|
} else if ("HighestLoseLife".equals(logic)) {
|
||||||
return MyRandom.getRandom().nextInt(Math.max(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1;
|
return Math.min(player.getLife() -1,MyRandom.getRandom().nextInt(Math.max(player.getLife() / 3, player.getWeakestOpponent().getLife())) + 1);
|
||||||
} else if ("HighestGetCounter".equals(logic)) {
|
} else if ("HighestGetCounter".equals(logic)) {
|
||||||
return MyRandom.getRandom().nextInt(3);
|
return MyRandom.getRandom().nextInt(3);
|
||||||
} else if (source.hasSVar("EnergyToPay")) {
|
} else if (source.hasSVar("EnergyToPay")) {
|
||||||
return AbilityUtils.calculateAmount(source, source.getSVar("EnergyToPay"), sa);
|
return AbilityUtils.calculateAmount(source, source.getSVar("EnergyToPay"), sa);
|
||||||
|
} else if ("Vermin".equals(logic)) {
|
||||||
|
return MyRandom.getRandom().nextInt(Math.max(player.getLife() - 5, 0));
|
||||||
}
|
}
|
||||||
return max;
|
return max;
|
||||||
}
|
}
|
||||||
@@ -1954,7 +2004,6 @@ public class AiController {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// this is where the computer cheats
|
// this is where the computer cheats
|
||||||
// changes AllZone.getComputerPlayer().getZone(Zone.Library)
|
// changes AllZone.getComputerPlayer().getZone(Zone.Library)
|
||||||
|
|
||||||
@@ -2007,11 +2056,6 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check
|
|
||||||
for (int i = 0; i < library.size(); i++) {
|
|
||||||
System.out.println(library.get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
return library;
|
return library;
|
||||||
} // smoothComputerManaCurve()
|
} // smoothComputerManaCurve()
|
||||||
|
|
||||||
@@ -2089,8 +2133,11 @@ public class AiController {
|
|||||||
if (useSimulation) {
|
if (useSimulation) {
|
||||||
return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getApi() == ApiType.Explore) {
|
if (sa.getApi() == ApiType.Explore) {
|
||||||
return ExploreAi.shouldPutInGraveyard(fetchList, decider);
|
return ExploreAi.shouldPutInGraveyard(fetchList, decider);
|
||||||
|
} else if (sa.getApi() == ApiType.Learn) {
|
||||||
|
return LearnAi.chooseCardToLearn(fetchList, decider, sa);
|
||||||
} else {
|
} else {
|
||||||
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
||||||
}
|
}
|
||||||
@@ -2231,10 +2278,8 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AI logic for choosing which replacement effect to apply
|
// AI logic for choosing which replacement effect to apply happens here.
|
||||||
// happens here.
|
|
||||||
return Iterables.getFirst(list, null);
|
return Iterables.getFirst(list, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import forge.game.cost.*;
|
||||||
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.card.CardType;
|
import forge.card.CardType;
|
||||||
@@ -22,43 +24,12 @@ import forge.game.card.CardLists;
|
|||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.cost.CostAddMana;
|
|
||||||
import forge.game.cost.CostChooseCreatureType;
|
|
||||||
import forge.game.cost.CostDamage;
|
|
||||||
import forge.game.cost.CostDecisionMakerBase;
|
|
||||||
import forge.game.cost.CostDiscard;
|
|
||||||
import forge.game.cost.CostDraw;
|
|
||||||
import forge.game.cost.CostExert;
|
|
||||||
import forge.game.cost.CostExile;
|
|
||||||
import forge.game.cost.CostExileFromStack;
|
|
||||||
import forge.game.cost.CostExiledMoveToGrave;
|
|
||||||
import forge.game.cost.CostFlipCoin;
|
|
||||||
import forge.game.cost.CostGainControl;
|
|
||||||
import forge.game.cost.CostGainLife;
|
|
||||||
import forge.game.cost.CostMill;
|
|
||||||
import forge.game.cost.CostPartMana;
|
|
||||||
import forge.game.cost.CostPayEnergy;
|
|
||||||
import forge.game.cost.CostPayLife;
|
|
||||||
import forge.game.cost.CostPutCardToLib;
|
|
||||||
import forge.game.cost.CostPutCounter;
|
|
||||||
import forge.game.cost.CostRemoveAnyCounter;
|
|
||||||
import forge.game.cost.CostRemoveCounter;
|
|
||||||
import forge.game.cost.CostReturn;
|
|
||||||
import forge.game.cost.CostReveal;
|
|
||||||
import forge.game.cost.CostSacrifice;
|
|
||||||
import forge.game.cost.CostTap;
|
|
||||||
import forge.game.cost.CostTapType;
|
|
||||||
import forge.game.cost.CostUnattach;
|
|
||||||
import forge.game.cost.CostUntap;
|
|
||||||
import forge.game.cost.CostUntapType;
|
|
||||||
import forge.game.cost.PaymentDecision;
|
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.SpellAbilityStackInstance;
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.Localizer;
|
|
||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
|
|
||||||
@@ -92,15 +63,9 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PaymentDecision visit(CostChooseCreatureType cost) {
|
public PaymentDecision visit(CostChooseCreatureType cost) {
|
||||||
Integer amount = cost.convertAmount();
|
String choice = player.getController().chooseSomeType("Creature", ability, CardType.getAllCreatureTypes(),
|
||||||
Iterable<String> choices = player.getController().chooseSomeType(
|
Lists.newArrayList());
|
||||||
Localizer.getInstance().getMessage("lblCreature"), ability, amount, amount, Lists.newArrayList(CardType.getAllCreatureTypes()));
|
return PaymentDecision.type(choice);
|
||||||
|
|
||||||
if (choices == null || Iterables.isEmpty(choices)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return PaymentDecision.types(choices);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -113,15 +78,13 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return PaymentDecision.card(player.getLastDrawnCard());
|
return PaymentDecision.card(player.getLastDrawnCard());
|
||||||
}
|
} else if (cost.payCostFromSource()) {
|
||||||
else if (cost.payCostFromSource()) {
|
|
||||||
if (!hand.contains(source)) {
|
if (!hand.contains(source)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return PaymentDecision.card(source);
|
return PaymentDecision.card(source);
|
||||||
}
|
} else if (type.equals("Hand")) {
|
||||||
else if (type.equals("Hand")) {
|
|
||||||
if (hand.size() > 1 && ability.getActivatingPlayer() != null) {
|
if (hand.size() > 1 && ability.getActivatingPlayer() != null) {
|
||||||
hand = ability.getActivatingPlayer().getController().orderMoveToZoneList(hand, ZoneType.Graveyard, ability);
|
hand = ability.getActivatingPlayer().getController().orderMoveToZoneList(hand, ZoneType.Graveyard, ability);
|
||||||
}
|
}
|
||||||
@@ -142,8 +105,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
randomSubset = ability.getActivatingPlayer().getController().orderMoveToZoneList(randomSubset, ZoneType.Graveyard, ability);
|
randomSubset = ability.getActivatingPlayer().getController().orderMoveToZoneList(randomSubset, ZoneType.Graveyard, ability);
|
||||||
}
|
}
|
||||||
return PaymentDecision.card(randomSubset);
|
return PaymentDecision.card(randomSubset);
|
||||||
}
|
} else if (type.equals("DifferentNames")) {
|
||||||
else if (type.equals("DifferentNames")) {
|
|
||||||
CardCollection differentNames = new CardCollection();
|
CardCollection differentNames = new CardCollection();
|
||||||
CardCollection discardMe = CardLists.filter(hand, CardPredicates.hasSVar("DiscardMe"));
|
CardCollection discardMe = CardLists.filter(hand, CardPredicates.hasSVar("DiscardMe"));
|
||||||
while (c > 0) {
|
while (c > 0) {
|
||||||
@@ -160,8 +122,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
c--;
|
c--;
|
||||||
}
|
}
|
||||||
return PaymentDecision.card(differentNames);
|
return PaymentDecision.card(differentNames);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
||||||
|
|
||||||
CardCollection result = aic.getCardsToDiscard(c, type.split(";"), ability, discarded);
|
CardCollection result = aic.getCardsToDiscard(c, type.split(";"), ability, discarded);
|
||||||
@@ -218,8 +179,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
else if (cost.sameZone) {
|
else if (cost.sameZone) {
|
||||||
// TODO Determine exile from same zone for AI
|
// TODO Determine exile from same zone for AI
|
||||||
return null;
|
return null;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
CardCollectionView chosen = ComputerUtil.chooseExileFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c, ability);
|
CardCollectionView chosen = ComputerUtil.chooseExileFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c, ability);
|
||||||
return null == chosen ? null : PaymentDecision.card(chosen);
|
return null == chosen ? null : PaymentDecision.card(chosen);
|
||||||
}
|
}
|
||||||
@@ -302,6 +262,15 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
return PaymentDecision.number(c);
|
return PaymentDecision.number(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostRollDice cost) {
|
||||||
|
Integer c = cost.convertAmount();
|
||||||
|
if (c == null) {
|
||||||
|
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||||
|
}
|
||||||
|
return PaymentDecision.number(c);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PaymentDecision visit(CostGainControl cost) {
|
public PaymentDecision visit(CostGainControl cost) {
|
||||||
if (cost.payCostFromSource()) {
|
if (cost.payCostFromSource()) {
|
||||||
@@ -389,7 +358,6 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
return PaymentDecision.number(c);
|
return PaymentDecision.number(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PaymentDecision visit(CostPutCardToLib cost) {
|
public PaymentDecision visit(CostPutCardToLib cost) {
|
||||||
if (cost.payCostFromSource()) {
|
if (cost.payCostFromSource()) {
|
||||||
@@ -402,8 +370,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
|
|
||||||
if (cost.isSameZone()) {
|
if (cost.isSameZone()) {
|
||||||
list = new CardCollection(game.getCardsIn(cost.getFrom()));
|
list = new CardCollection(game.getCardsIn(cost.getFrom()));
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
list = new CardCollection(player.getCardsIn(cost.getFrom()));
|
list = new CardCollection(player.getCardsIn(cost.getFrom()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,7 +484,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
return PaymentDecision.card(source);
|
return PaymentDecision.card(source);
|
||||||
}
|
}
|
||||||
if (cost.getType().equals("OriginalHost")) {
|
if (cost.getType().equals("OriginalHost")) {
|
||||||
return PaymentDecision.card(ability.getHostCard());
|
return PaymentDecision.card(ability.getOriginalHost());
|
||||||
}
|
}
|
||||||
if (cost.getAmount().equals("All")) {
|
if (cost.getAmount().equals("All")) {
|
||||||
// Does the AI want to use Sacrifice All?
|
// Does the AI want to use Sacrifice All?
|
||||||
@@ -569,7 +536,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cost.getRevealFrom().equals(ZoneType.Exile)) {
|
if (cost.getRevealFrom().get(0).equals(ZoneType.Exile)) {
|
||||||
hand = CardLists.getValidCards(hand, type.split(";"), player, source, ability);
|
hand = CardLists.getValidCards(hand, type.split(";"), player, source, ability);
|
||||||
return PaymentDecision.card(getBestCreatureAI(hand));
|
return PaymentDecision.card(getBestCreatureAI(hand));
|
||||||
}
|
}
|
||||||
@@ -583,6 +550,11 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
|
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentDecision visit(CostRevealChosenPlayer cost) {
|
||||||
|
return PaymentDecision.number(1);
|
||||||
|
}
|
||||||
|
|
||||||
protected int removeCounter(GameEntityCounterTable table, List<Card> prefs, CounterEnumType cType, int stillToRemove) {
|
protected int removeCounter(GameEntityCounterTable table, List<Card> prefs, CounterEnumType cType, int stillToRemove) {
|
||||||
int removed = 0;
|
int removed = 0;
|
||||||
if (!prefs.isEmpty() && stillToRemove > 0) {
|
if (!prefs.isEmpty() && stillToRemove > 0) {
|
||||||
@@ -596,7 +568,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove);
|
int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove);
|
||||||
if (thisRemove > 0) {
|
if (thisRemove > 0) {
|
||||||
removed += thisRemove;
|
removed += thisRemove;
|
||||||
table.put(prefCard, CounterType.get(cType), thisRemove);
|
table.put(null, prefCard, CounterType.get(cType), thisRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -607,7 +579,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
public PaymentDecision visit(CostRemoveAnyCounter cost) {
|
public PaymentDecision visit(CostRemoveAnyCounter cost) {
|
||||||
final String amount = cost.getAmount();
|
final String amount = cost.getAmount();
|
||||||
final int c = AbilityUtils.calculateAmount(source, amount, ability);
|
final int c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||||
final Card originalHost = ability.getOriginalOrHost();
|
final Card originalHost = ObjectUtils.defaultIfNull(ability.getOriginalHost(), source);
|
||||||
|
|
||||||
if (c <= 0) {
|
if (c <= 0) {
|
||||||
return null;
|
return null;
|
||||||
@@ -628,7 +600,6 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
// currently if amount is bigger than one,
|
// currently if amount is bigger than one,
|
||||||
// it tries to remove all counters from one source and type at once
|
// it tries to remove all counters from one source and type at once
|
||||||
|
|
||||||
|
|
||||||
int toRemove = 0;
|
int toRemove = 0;
|
||||||
final GameEntityCounterTable table = new GameEntityCounterTable();
|
final GameEntityCounterTable table = new GameEntityCounterTable();
|
||||||
|
|
||||||
@@ -662,7 +633,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
int thisRemove = Math.min(card.getCounters(ctype), c - toRemove);
|
int thisRemove = Math.min(card.getCounters(ctype), c - toRemove);
|
||||||
if (thisRemove > 0) {
|
if (thisRemove > 0) {
|
||||||
toRemove += thisRemove;
|
toRemove += thisRemove;
|
||||||
table.put(card, ctype, thisRemove);
|
table.put(null, card, ctype, thisRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -690,7 +661,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
int over = Math.min(e.getValue(), c - toRemove);
|
int over = Math.min(e.getValue(), c - toRemove);
|
||||||
if (over > 0) {
|
if (over > 0) {
|
||||||
toRemove += over;
|
toRemove += over;
|
||||||
table.put(crd, e.getKey(), over);
|
table.put(null, crd, e.getKey(), over);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -720,7 +691,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
int over = Math.min(e.getValue(), c - toRemove);
|
int over = Math.min(e.getValue(), c - toRemove);
|
||||||
if (over > 0) {
|
if (over > 0) {
|
||||||
toRemove += over;
|
toRemove += over;
|
||||||
table.put(crd, e.getKey(), over);
|
table.put(null, crd, e.getKey(), over);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -760,7 +731,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove);
|
int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove);
|
||||||
if (over > 0) {
|
if (over > 0) {
|
||||||
toRemove += over;
|
toRemove += over;
|
||||||
table.put(crd, CounterType.get(CounterEnumType.QUEST), over);
|
table.put(null, crd, CounterType.get(CounterEnumType.QUEST), over);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -784,7 +755,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
int thisRemove = Math.min(card.getCounters(cost.counter), c - toRemove);
|
int thisRemove = Math.min(card.getCounters(cost.counter), c - toRemove);
|
||||||
if (thisRemove > 0) {
|
if (thisRemove > 0) {
|
||||||
toRemove += thisRemove;
|
toRemove += thisRemove;
|
||||||
table.put(card, cost.counter, thisRemove);
|
table.put(null, card, cost.counter, thisRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -798,7 +769,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
int thisRemove = Math.min(e.getValue(), c - toRemove);
|
int thisRemove = Math.min(e.getValue(), c - toRemove);
|
||||||
if (thisRemove > 0) {
|
if (thisRemove > 0) {
|
||||||
toRemove += thisRemove;
|
toRemove += thisRemove;
|
||||||
table.put(card, e.getKey(), thisRemove);
|
table.put(null, card, e.getKey(), thisRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -893,4 +864,3 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -188,7 +188,6 @@ public class ComputerUtil {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
|
||||||
|
|
||||||
// Play higher costing spells first?
|
// Play higher costing spells first?
|
||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
|
|
||||||
@@ -213,7 +212,8 @@ public class ComputerUtil {
|
|||||||
if (unless != null && !unless.endsWith(">")) {
|
if (unless != null && !unless.endsWith(">")) {
|
||||||
final int amount = AbilityUtils.calculateAmount(source, unless, sa);
|
final int amount = AbilityUtils.calculateAmount(source, unless, sa);
|
||||||
|
|
||||||
final int usableManaSources = ComputerUtilMana.getAvailableManaSources(ComputerUtil.getOpponentFor(ai), true).size();
|
// this is enough as long as the AI is only smart enough to target top of stack
|
||||||
|
final int usableManaSources = ComputerUtilMana.getAvailableManaSources(ComputerUtilAbility.getTopSpellAbilityOnStack(ai.getGame(), sa).getActivatingPlayer(), true).size();
|
||||||
|
|
||||||
// If the Unless isn't enough, this should be less likely to be used
|
// If the Unless isn't enough, this should be less likely to be used
|
||||||
if (amount > usableManaSources) {
|
if (amount > usableManaSources) {
|
||||||
@@ -280,7 +280,7 @@ public class ComputerUtil {
|
|||||||
SpellAbility newSA = sa.copyWithNoManaCost();
|
SpellAbility newSA = sa.copyWithNoManaCost();
|
||||||
newSA.setActivatingPlayer(ai);
|
newSA.setActivatingPlayer(ai);
|
||||||
|
|
||||||
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA)) {
|
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA) || !ComputerUtilMana.canPayManaCost(newSA, ai, 0)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,11 +331,14 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Card getCardPreference(final Player ai, final Card activate, final String pref, final CardCollection typeList) {
|
public static Card getCardPreference(final Player ai, final Card activate, final String pref, final CardCollection typeList) {
|
||||||
|
return getCardPreference(ai, activate, pref, typeList, null);
|
||||||
|
}
|
||||||
|
public static Card getCardPreference(final Player ai, final Card activate, final String pref, final CardCollection typeList, SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
String prefDef = "";
|
String prefDef = "";
|
||||||
if (activate != null) {
|
if (activate != null) {
|
||||||
prefDef = activate.getSVar("AIPreference");
|
prefDef = activate.getSVar("AIPreference");
|
||||||
final String[] prefGroups = activate.getSVar("AIPreference").split("\\|");
|
final String[] prefGroups = prefDef.split("\\|");
|
||||||
for (String prefGroup : prefGroups) {
|
for (String prefGroup : prefGroups) {
|
||||||
final String[] prefValid = prefGroup.trim().split("\\$");
|
final String[] prefValid = prefGroup.trim().split("\\$");
|
||||||
if (prefValid[0].equals(pref) && !prefValid[1].startsWith("Special:")) {
|
if (prefValid[0].equals(pref) && !prefValid[1].startsWith("Special:")) {
|
||||||
@@ -346,8 +349,8 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
for (String validItem : prefValid[1].split(",")) {
|
for (String validItem : prefValid[1].split(",")) {
|
||||||
final CardCollection prefList = CardLists.getValidCards(typeList, validItem, activate.getController(), activate, null);
|
final CardCollection prefList = CardLists.getValidCards(typeList, validItem, activate.getController(), activate, null);
|
||||||
int threshold = getAIPreferenceParameter(activate, "CreatureEvalThreshold");
|
int threshold = getAIPreferenceParameter(activate, "CreatureEvalThreshold", sa);
|
||||||
int minNeeded = getAIPreferenceParameter(activate, "MinCreaturesBelowThreshold");
|
int minNeeded = getAIPreferenceParameter(activate, "MinCreaturesBelowThreshold", sa);
|
||||||
|
|
||||||
if (threshold != -1) {
|
if (threshold != -1) {
|
||||||
List<Card> toRemove = Lists.newArrayList();
|
List<Card> toRemove = Lists.newArrayList();
|
||||||
@@ -390,7 +393,7 @@ public class ComputerUtil {
|
|||||||
final CardCollection sacMeList = CardLists.filter(typeList, new Predicate<Card>() {
|
final CardCollection sacMeList = CardLists.filter(typeList, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return (c.hasSVar("SacMe") && (Integer.parseInt(c.getSVar("SacMe")) == priority));
|
return c.hasSVar("SacMe") && (Integer.parseInt(c.getSVar("SacMe")) == priority);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!sacMeList.isEmpty()) {
|
if (!sacMeList.isEmpty()) {
|
||||||
@@ -419,6 +422,7 @@ public class ComputerUtil {
|
|||||||
if (!nonCreatures.isEmpty()) {
|
if (!nonCreatures.isEmpty()) {
|
||||||
return ComputerUtilCard.getWorstAI(nonCreatures);
|
return ComputerUtilCard.getWorstAI(nonCreatures);
|
||||||
} else if (!typeList.isEmpty()) {
|
} else if (!typeList.isEmpty()) {
|
||||||
|
// TODO make sure survival is possible in case the creature blocks a trampler
|
||||||
return ComputerUtilCard.getWorstAI(typeList);
|
return ComputerUtilCard.getWorstAI(typeList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -505,7 +509,7 @@ public class ComputerUtil {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getAIPreferenceParameter(final Card c, final String paramName) {
|
public static int getAIPreferenceParameter(final Card c, final String paramName, SpellAbility sa) {
|
||||||
if (!c.hasSVar("AIPreferenceParams")) {
|
if (!c.hasSVar("AIPreferenceParams")) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -520,7 +524,21 @@ public class ComputerUtil {
|
|||||||
case "CreatureEvalThreshold":
|
case "CreatureEvalThreshold":
|
||||||
// Threshold of 150 is just below the level of a 1/1 mana dork or a 2/2 baseline creature with no keywords
|
// Threshold of 150 is just below the level of a 1/1 mana dork or a 2/2 baseline creature with no keywords
|
||||||
if (paramName.equals(parName)) {
|
if (paramName.equals(parName)) {
|
||||||
return Integer.parseInt(parValue);
|
int num = 0;
|
||||||
|
try {
|
||||||
|
num = Integer.parseInt(parValue);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
String[] valParts = StringUtils.split(parValue, "/");
|
||||||
|
CardCollection foundCards = AbilityUtils.getDefinedCards(c, valParts[0], sa);
|
||||||
|
if (!foundCards.isEmpty()) {
|
||||||
|
num = ComputerUtilCard.evaluateCreature(foundCards.get(0));
|
||||||
|
}
|
||||||
|
valParts[0] = Integer.toString(num);
|
||||||
|
if (valParts.length > 1) {
|
||||||
|
num = AbilityUtils.doXMath(num, valParts[1], c, sa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return num;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "MinCreaturesBelowThreshold":
|
case "MinCreaturesBelowThreshold":
|
||||||
@@ -543,9 +561,8 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability));
|
typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability));
|
||||||
|
|
||||||
if ((target != null) && target.getController() == ai) {
|
// don't sacrifice the card we're pumping
|
||||||
typeList.remove(target); // don't sacrifice the card we're pumping
|
typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, ability, ai);
|
||||||
}
|
|
||||||
|
|
||||||
if (typeList.size() < amount) {
|
if (typeList.size() < amount) {
|
||||||
return null;
|
return null;
|
||||||
@@ -573,9 +590,8 @@ public class ComputerUtil {
|
|||||||
final Card target, final int amount, SpellAbility sa) {
|
final Card target, final int amount, SpellAbility sa) {
|
||||||
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa);
|
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa);
|
||||||
|
|
||||||
if ((target != null) && target.getController() == ai) {
|
// don't exile the card we're pumping
|
||||||
typeList.remove(target); // don't exile the card we're pumping
|
typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai);
|
||||||
}
|
|
||||||
|
|
||||||
if (typeList.size() < amount) {
|
if (typeList.size() < amount) {
|
||||||
return null;
|
return null;
|
||||||
@@ -594,9 +610,8 @@ public class ComputerUtil {
|
|||||||
final Card target, final int amount, SpellAbility sa) {
|
final Card target, final int amount, SpellAbility sa) {
|
||||||
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa);
|
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa);
|
||||||
|
|
||||||
if ((target != null) && target.getController() == ai) {
|
// don't move the card we're pumping
|
||||||
typeList.remove(target); // don't move the card we're pumping
|
typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai);
|
||||||
}
|
|
||||||
|
|
||||||
if (typeList.size() < amount) {
|
if (typeList.size() < amount) {
|
||||||
return null;
|
return null;
|
||||||
@@ -718,12 +733,10 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static CardCollection chooseReturnType(final Player ai, final String type, final Card activate, final Card target, final int amount, SpellAbility sa) {
|
public static CardCollection chooseReturnType(final Player ai, final String type, final Card activate, final Card target, final int amount, SpellAbility sa) {
|
||||||
final CardCollection typeList =
|
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
|
||||||
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
|
|
||||||
if ((target != null) && target.getController() == ai) {
|
|
||||||
// don't bounce the card we're pumping
|
// don't bounce the card we're pumping
|
||||||
typeList.remove(target);
|
typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai);
|
||||||
}
|
|
||||||
|
|
||||||
if (typeList.size() < amount) {
|
if (typeList.size() < amount) {
|
||||||
return new CardCollection();
|
return new CardCollection();
|
||||||
@@ -743,7 +756,7 @@ public class ComputerUtil {
|
|||||||
CardCollection remaining = new CardCollection(cardlist);
|
CardCollection remaining = new CardCollection(cardlist);
|
||||||
final CardCollection sacrificed = new CardCollection();
|
final CardCollection sacrificed = new CardCollection();
|
||||||
final Card host = source.getHostCard();
|
final Card host = source.getHostCard();
|
||||||
final int considerSacThreshold = getAIPreferenceParameter(host, "CreatureEvalThreshold");
|
final int considerSacThreshold = getAIPreferenceParameter(host, "CreatureEvalThreshold", source);
|
||||||
|
|
||||||
if ("OpponentOnly".equals(source.getParam("AILogic"))) {
|
if ("OpponentOnly".equals(source.getParam("AILogic"))) {
|
||||||
if(!source.getActivatingPlayer().isOpponentOf(ai)) {
|
if(!source.getActivatingPlayer().isOpponentOf(ai)) {
|
||||||
@@ -1068,9 +1081,6 @@ public class ComputerUtil {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (card.isEquipment() && buffedcard.isCreature() && CombatUtil.canAttack(buffedcard, ComputerUtil.getOpponentFor(ai))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (card.isCreature()) {
|
if (card.isCreature()) {
|
||||||
if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
|
if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
|
||||||
return true;
|
return true;
|
||||||
@@ -1093,8 +1103,8 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
} // BuffedBy
|
} // BuffedBy
|
||||||
|
|
||||||
// get all cards the human controls with AntiBuffedBy
|
// there's a good chance AI will attack weak target
|
||||||
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
|
||||||
for (Card buffedcard : antibuffed) {
|
for (Card buffedcard : antibuffed) {
|
||||||
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
||||||
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
||||||
@@ -1128,7 +1138,7 @@ public class ComputerUtil {
|
|||||||
creatures2.add(creatures.get(i));
|
creatures2.add(creatures.get(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (((creatures2.size() + CardUtil.getThisTurnCast("Creature.YouCtrl", vengevines.get(0)).size()) > 1)
|
if (((creatures2.size() + CardUtil.getThisTurnCast("Creature.YouCtrl", vengevines.get(0), null).size()) > 1)
|
||||||
&& card.isCreature() && card.getManaCost().getCMC() <= 3) {
|
&& card.isCreature() && card.getManaCost().getCMC() <= 3) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1142,27 +1152,16 @@ public class ComputerUtil {
|
|||||||
* @return true if it's OK to cast this Card for less than the max targets
|
* @return true if it's OK to cast this Card for less than the max targets
|
||||||
*/
|
*/
|
||||||
public static boolean shouldCastLessThanMax(final Player ai, final Card source) {
|
public static boolean shouldCastLessThanMax(final Player ai, final Card source) {
|
||||||
boolean ret = true;
|
if (source.getXManaCostPaid() > 0) {
|
||||||
if (source.getManaCost().countX() > 0) {
|
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by payment resources available.
|
||||||
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available.
|
return true;
|
||||||
return ret;
|
}
|
||||||
} else {
|
if (aiLifeInDanger(ai, false, 0)) {
|
||||||
// Otherwise, if life is possibly in danger, then this is fine.
|
// Otherwise, if life is possibly in danger, then this is fine.
|
||||||
Combat combat = new Combat(ComputerUtil.getOpponentFor(ai));
|
return true;
|
||||||
CardCollectionView attackers = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
|
|
||||||
for (Card att : attackers) {
|
|
||||||
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
|
||||||
combat.addAttacker(att, ComputerUtil.getOpponentFor(att.getController()));
|
|
||||||
}
|
}
|
||||||
}
|
// do not play now.
|
||||||
AiBlockController aiBlock = new AiBlockController(ai);
|
return false;
|
||||||
aiBlock.assignBlockersForCombat(combat);
|
|
||||||
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
|
||||||
// Otherwise, return false. Do not play now.
|
|
||||||
ret = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1266,8 +1265,8 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all cards the human controls with AntiBuffedBy
|
// there's a good chance AI will attack weak target
|
||||||
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
|
||||||
for (Card buffedcard : antibuffed) {
|
for (Card buffedcard : antibuffed) {
|
||||||
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
||||||
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
||||||
@@ -1308,7 +1307,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
if (abCost.hasTapCost() && source.hasSVar("AITapDown")) {
|
if (abCost.hasTapCost() && source.hasSVar("AITapDown")) {
|
||||||
return true;
|
return true;
|
||||||
} else if (sa.hasParam("Planeswalker") && ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {
|
} else if (sa.isPwAbility() && ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {
|
||||||
for (final CostPart part : abCost.getCostParts()) {
|
for (final CostPart part : abCost.getCostParts()) {
|
||||||
if (part instanceof CostPutCounter) {
|
if (part instanceof CostPutCounter) {
|
||||||
return true;
|
return true;
|
||||||
@@ -1463,7 +1462,7 @@ public class ComputerUtil {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int possibleNonCombatDamage(Player ai) {
|
public static int possibleNonCombatDamage(Player ai, Player enemy) {
|
||||||
int damage = 0;
|
int damage = 0;
|
||||||
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||||
all.addAll(ai.getCardsActivableInExternalZones(true));
|
all.addAll(ai.getCardsActivableInExternalZones(true));
|
||||||
@@ -1483,7 +1482,6 @@ public class ComputerUtil {
|
|||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final Player enemy = ComputerUtil.getOpponentFor(ai);
|
|
||||||
if (!sa.canTarget(enemy)) {
|
if (!sa.canTarget(enemy)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -2068,7 +2066,7 @@ public class ComputerUtil {
|
|||||||
// Computer mulligans if there are no cards with converted mana cost of 0 in its hand
|
// Computer mulligans if there are no cards with converted mana cost of 0 in its hand
|
||||||
public static boolean wantMulligan(Player ai, int cardsToReturn) {
|
public static boolean wantMulligan(Player ai, int cardsToReturn) {
|
||||||
final CardCollectionView handList = ai.getCardsIn(ZoneType.Hand);
|
final CardCollectionView handList = ai.getCardsIn(ZoneType.Hand);
|
||||||
return scoreHand(handList, ai, cardsToReturn) <= 0;
|
return !handList.isEmpty() && scoreHand(handList, ai, cardsToReturn) <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CardCollection getPartialParisCandidates(Player ai) {
|
public static CardCollection getPartialParisCandidates(Player ai) {
|
||||||
@@ -2256,24 +2254,24 @@ public class ComputerUtil {
|
|||||||
return getCardsToDiscardFromOpponent(aiChooser, p, sa, validCards, min, max);
|
return getCardsToDiscardFromOpponent(aiChooser, p, sa, validCards, min, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> chooseSomeType(Player ai, String kindOfType, String logic, int min, int max, Collection<String> validTypes) {
|
public static String chooseSomeType(Player ai, String kindOfType, String logic, Collection<String> validTypes, List<String> invalidTypes) {
|
||||||
|
if (invalidTypes == null) {
|
||||||
|
invalidTypes = ImmutableList.of();
|
||||||
|
}
|
||||||
if (validTypes == null) {
|
if (validTypes == null) {
|
||||||
validTypes = ImmutableList.of();
|
validTypes = ImmutableList.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
List<String> chosenList = Lists.newArrayList();
|
|
||||||
if (kindOfType.equals("Card")) {
|
|
||||||
String chosen = "";
|
String chosen = "";
|
||||||
|
if (kindOfType.equals("Card")) {
|
||||||
// TODO
|
// TODO
|
||||||
// computer will need to choose a type
|
// computer will need to choose a type based on whether it needs a creature or land,
|
||||||
// based on whether it needs a creature or land,
|
// otherwise, lib search for most common type left then, reveal chosenType to Human
|
||||||
// otherwise, lib search for most common type left
|
|
||||||
// then, reveal chosenType to Human
|
|
||||||
if (game.getPhaseHandler().is(PhaseType.UNTAP) && logic == null) { // Storage Matrix
|
if (game.getPhaseHandler().is(PhaseType.UNTAP) && logic == null) { // Storage Matrix
|
||||||
double amount = 0;
|
double amount = 0;
|
||||||
for (String type : validTypes) {
|
for (String type : CardType.getAllCardTypes()) {
|
||||||
|
if (!invalidTypes.contains(type)) {
|
||||||
CardCollection list = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(type), Presets.TAPPED);
|
CardCollection list = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isType(type), Presets.TAPPED);
|
||||||
double i = type.equals("Creature") ? list.size() * 1.5 : list.size();
|
double i = type.equals("Creature") ? list.size() * 1.5 : list.size();
|
||||||
if (i > amount) {
|
if (i > amount) {
|
||||||
@@ -2281,17 +2279,15 @@ public class ComputerUtil {
|
|||||||
chosen = type;
|
chosen = type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (logic == "MostProminentInComputerDeck") {
|
}
|
||||||
chosen = ComputerUtilCard.getMostProminentType(ai.getAllCards(), validTypes);
|
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(chosen)) {
|
if (StringUtils.isEmpty(chosen)) {
|
||||||
chosen = validTypes.isEmpty() ? "Creature" : Aggregates.random(validTypes);
|
chosen = validTypes.isEmpty() ? "Creature" : Aggregates.random(validTypes);
|
||||||
}
|
}
|
||||||
chosenList.add(chosen);
|
|
||||||
} else if (kindOfType.equals("Creature")) {
|
} else if (kindOfType.equals("Creature")) {
|
||||||
String chosen = "";
|
|
||||||
if (logic != null) {
|
if (logic != null) {
|
||||||
List <String> valid = Lists.newArrayList(validTypes);
|
List <String> valid = Lists.newArrayList(CardType.getAllCreatureTypes());
|
||||||
|
valid.removeAll(invalidTypes);
|
||||||
|
|
||||||
if (logic.equals("MostProminentOnBattlefield")) {
|
if (logic.equals("MostProminentOnBattlefield")) {
|
||||||
chosen = ComputerUtilCard.getMostProminentType(game.getCardsIn(ZoneType.Battlefield), valid);
|
chosen = ComputerUtilCard.getMostProminentType(game.getCardsIn(ZoneType.Battlefield), valid);
|
||||||
@@ -2302,7 +2298,7 @@ public class ComputerUtil {
|
|||||||
else if (logic.equals("MostProminentOppControls")) {
|
else if (logic.equals("MostProminentOppControls")) {
|
||||||
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||||
chosen = ComputerUtilCard.getMostProminentType(list, valid);
|
chosen = ComputerUtilCard.getMostProminentType(list, valid);
|
||||||
if (!CardType.isACreatureType(chosen) || !validTypes.contains(chosen)) {
|
if (!CardType.isACreatureType(chosen) || invalidTypes.contains(chosen)) {
|
||||||
list = CardLists.filterControlledBy(game.getCardsInGame(), ai.getOpponents());
|
list = CardLists.filterControlledBy(game.getCardsInGame(), ai.getOpponents());
|
||||||
chosen = ComputerUtilCard.getMostProminentType(list, valid);
|
chosen = ComputerUtilCard.getMostProminentType(list, valid);
|
||||||
}
|
}
|
||||||
@@ -2314,17 +2310,16 @@ public class ComputerUtil {
|
|||||||
chosen = ComputerUtilCard.getMostProminentType(ai.getCardsIn(ZoneType.Graveyard), valid);
|
chosen = ComputerUtilCard.getMostProminentType(ai.getCardsIn(ZoneType.Graveyard), valid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!CardType.isACreatureType(chosen) || !validTypes.contains(chosen)) {
|
if (!CardType.isACreatureType(chosen) || invalidTypes.contains(chosen)) {
|
||||||
chosen = Iterables.getFirst(validTypes, null);
|
chosen = "Sliver";
|
||||||
}
|
}
|
||||||
|
|
||||||
chosenList.add(chosen);
|
|
||||||
} else if (kindOfType.equals("Basic Land")) {
|
} else if (kindOfType.equals("Basic Land")) {
|
||||||
String chosen = "";
|
|
||||||
if (logic != null) {
|
if (logic != null) {
|
||||||
if (logic.equals("MostProminentOppControls")) {
|
if (logic.equals("MostProminentOppControls")) {
|
||||||
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||||
List<String> valid = Lists.newArrayList(validTypes);
|
List<String> valid = Lists.newArrayList(CardType.getBasicTypes());
|
||||||
|
valid.removeAll(invalidTypes);
|
||||||
|
|
||||||
chosen = ComputerUtilCard.getMostProminentType(list, valid);
|
chosen = ComputerUtilCard.getMostProminentType(list, valid);
|
||||||
} else if (logic.equals("MostNeededType")) {
|
} else if (logic.equals("MostNeededType")) {
|
||||||
@@ -2347,9 +2342,9 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (logic.equals("ChosenLandwalk")) {
|
else if (logic.equals("ChosenLandwalk")) {
|
||||||
for (Card c : ai.getWeakestOpponent().getLandsInPlay()) {
|
for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
|
||||||
for (String t : c.getType()) {
|
for (String t : c.getType()) {
|
||||||
if (validTypes.contains(t) && CardType.isABasicLandType(t)) {
|
if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) {
|
||||||
chosen = t;
|
chosen = t;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2358,28 +2353,16 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CardType.isABasicLandType(chosen) || !validTypes.contains(chosen)) {
|
if (!CardType.isABasicLandType(chosen) || invalidTypes.contains(chosen)) {
|
||||||
chosen = Iterables.getFirst(validTypes, null);
|
chosen = "Island";
|
||||||
}
|
|
||||||
chosenList.add(chosen);
|
|
||||||
// the only one with choosing two types is Illusionary Terrain
|
|
||||||
// and it needs other AI logic
|
|
||||||
while (chosenList.size() < min) {
|
|
||||||
validTypes.remove(chosen);
|
|
||||||
chosen = Iterables.getFirst(validTypes, null);
|
|
||||||
if (chosen == null) {
|
|
||||||
return Lists.newArrayList();
|
|
||||||
}
|
|
||||||
chosenList.add(chosen);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (kindOfType.equals("Land")) {
|
else if (kindOfType.equals("Land")) {
|
||||||
String chosen = "";
|
|
||||||
if (logic != null) {
|
if (logic != null) {
|
||||||
if (logic.equals("ChosenLandwalk")) {
|
if (logic.equals("ChosenLandwalk")) {
|
||||||
for (Card c : ai.getWeakestOpponent().getLandsInPlay()) {
|
for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
|
||||||
for (String t : c.getType().getLandTypes()) {
|
for (String t : c.getType().getLandTypes()) {
|
||||||
if (validTypes.contains(t)) {
|
if (!invalidTypes.contains(t)) {
|
||||||
chosen = t;
|
chosen = t;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2388,11 +2371,10 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(chosen)) {
|
if (StringUtils.isEmpty(chosen)) {
|
||||||
chosen = Iterables.getFirst(validTypes, null);
|
chosen = "Island";
|
||||||
}
|
}
|
||||||
chosenList.add(chosen);
|
|
||||||
}
|
}
|
||||||
return chosenList;
|
return chosen;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Object vote(Player ai, List<Object> options, SpellAbility sa, Multimap<Object, Player> votes, Player forPlayer) {
|
public static Object vote(Player ai, List<Object> options, SpellAbility sa, Multimap<Object, Player> votes, Player forPlayer) {
|
||||||
@@ -2413,15 +2395,18 @@ public class ComputerUtil {
|
|||||||
case "Torture":
|
case "Torture":
|
||||||
return "Torture";
|
return "Torture";
|
||||||
case "GraceOrCondemnation":
|
case "GraceOrCondemnation":
|
||||||
return ai.getCreaturesInPlay().size() > ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size() ? "Grace"
|
List<ZoneType> graceZones = new ArrayList<ZoneType>();
|
||||||
: "Condemnation";
|
graceZones.add(ZoneType.Battlefield);
|
||||||
|
graceZones.add(ZoneType.Graveyard);
|
||||||
|
CardCollection graceCreatures = CardLists.getType(sa.getHostCard().getGame().getCardsIn(graceZones), "Creature");
|
||||||
|
int humanGrace = CardLists.filterControlledBy(graceCreatures, ai.getOpponents()).size();
|
||||||
|
int aiGrace = CardLists.filterControlledBy(graceCreatures, ai).size();
|
||||||
|
return aiGrace > humanGrace ? "Grace" : "Condemnation";
|
||||||
case "CarnageOrHomage":
|
case "CarnageOrHomage":
|
||||||
CardCollection cardsInPlay = CardLists
|
CardCollection cardsInPlay = CardLists.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
|
||||||
.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
|
|
||||||
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
|
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
|
||||||
CardCollection computerlist = CardLists.filterControlledBy(cardsInPlay, ai);
|
CardCollection computerlist = ai.getCreaturesInPlay();
|
||||||
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard
|
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
|
||||||
.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
|
|
||||||
case "Judgment":
|
case "Judgment":
|
||||||
if (votes.isEmpty()) {
|
if (votes.isEmpty()) {
|
||||||
CardCollection list = new CardCollection();
|
CardCollection list = new CardCollection();
|
||||||
@@ -2460,8 +2445,7 @@ public class ComputerUtil {
|
|||||||
if (!source.canReceiveCounters(p1p1Type)) {
|
if (!source.canReceiveCounters(p1p1Type)) {
|
||||||
return opponent ? "Feather" : "Quill";
|
return opponent ? "Feather" : "Quill";
|
||||||
}
|
}
|
||||||
// if source is not on the battlefield anymore, choose +1/+1
|
// if source is not on the battlefield anymore, choose +1/+1 ones
|
||||||
// ones
|
|
||||||
if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
|
if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
|
||||||
return opponent ? "Feather" : "Quill";
|
return opponent ? "Feather" : "Quill";
|
||||||
}
|
}
|
||||||
@@ -2503,8 +2487,7 @@ public class ComputerUtil {
|
|||||||
return opponent ? "Numbers" : "Strength";
|
return opponent ? "Numbers" : "Strength";
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO check for ETB to +1/+1 counters
|
// TODO check for ETB to +1/+1 counters or over another trigger like lifegain
|
||||||
// or over another trigger like lifegain
|
|
||||||
|
|
||||||
int tokenScore = ComputerUtilCard.evaluateCreature(token);
|
int tokenScore = ComputerUtilCard.evaluateCreature(token);
|
||||||
|
|
||||||
@@ -2582,8 +2565,7 @@ public class ComputerUtil {
|
|||||||
return "Taxes";
|
return "Taxes";
|
||||||
} else {
|
} else {
|
||||||
// ai is first voter or ally of controller
|
// ai is first voter or ally of controller
|
||||||
// both are not affected, but if opponents controll creatures,
|
// both are not affected, but if opponents control creatures, sacrifice is worse
|
||||||
// sacrifice is worse
|
|
||||||
return controller.getOpponents().getCreaturesInPlay().isEmpty() ? "Taxes" : "Death";
|
return controller.getOpponents().getCreaturesInPlay().isEmpty() ? "Taxes" : "Death";
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -2667,7 +2649,7 @@ public class ComputerUtil {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (trigger.hasParam("ValidCard")) {
|
if (trigger.hasParam("ValidCard")) {
|
||||||
if (!card.isValid(trigger.getParam("ValidCard"), source.getController(), source, sa)) {
|
if (!card.isValid(trigger.getParam("ValidCard").split(","), source.getController(), source, sa)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2850,7 +2832,6 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean lifegainPositive(final Player player, final Card source) {
|
public static boolean lifegainPositive(final Player player, final Card source) {
|
||||||
|
|
||||||
if (!player.canGainLife()) {
|
if (!player.canGainLife()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2878,9 +2859,7 @@ public class ComputerUtil {
|
|||||||
public static boolean lifegainNegative(final Player player, final Card source) {
|
public static boolean lifegainNegative(final Player player, final Card source) {
|
||||||
return lifegainNegative(player, source, 1);
|
return lifegainNegative(player, source, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean lifegainNegative(final Player player, final Card source, final int n) {
|
public static boolean lifegainNegative(final Player player, final Card source, final int n) {
|
||||||
|
|
||||||
if (!player.canGainLife()) {
|
if (!player.canGainLife()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2910,10 +2889,10 @@ public class ComputerUtil {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean targetPlayableSpellCard(final Player ai, CardCollection options, final SpellAbility sa, final boolean withoutPayingManaCost) {
|
public static boolean targetPlayableSpellCard(final Player ai, CardCollection options, final SpellAbility sa, final boolean withoutPayingManaCost, boolean mandatory) {
|
||||||
// determine and target a card with a SA that the AI can afford and will play
|
// determine and target a card with a SA that the AI can afford and will play
|
||||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||||
Card targetSpellCard = null;
|
CardCollection targets = new CardCollection();
|
||||||
for (Card c : options) {
|
for (Card c : options) {
|
||||||
if (withoutPayingManaCost && c.getManaCost() != null && c.getManaCost().countX() > 0) {
|
if (withoutPayingManaCost && c.getManaCost() != null && c.getManaCost().countX() > 0) {
|
||||||
// The AI will otherwise cheat with the mana payment, announcing X > 0 for spells like Heat Ray when replaying them
|
// The AI will otherwise cheat with the mana payment, announcing X > 0 for spells like Heat Ray when replaying them
|
||||||
@@ -2933,38 +2912,22 @@ public class ComputerUtil {
|
|||||||
// at this point, we're assuming that card will be castable from whichever zone it's in by the AI player.
|
// at this point, we're assuming that card will be castable from whichever zone it's in by the AI player.
|
||||||
abTest.setActivatingPlayer(ai);
|
abTest.setActivatingPlayer(ai);
|
||||||
abTest.getRestrictions().setZone(c.getZone().getZoneType());
|
abTest.getRestrictions().setZone(c.getZone().getZoneType());
|
||||||
final boolean play = AiPlayDecision.WillPlay == aic.canPlaySa(abTest);
|
if (AiPlayDecision.WillPlay == aic.canPlaySa(abTest) && ComputerUtilCost.canPayCost(abTest, ai)) {
|
||||||
final boolean pay = ComputerUtilCost.canPayCost(abTest, ai);
|
targets.add(c);
|
||||||
if (play && pay) {
|
|
||||||
targetSpellCard = c;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (targetSpellCard == null) {
|
if (targets.isEmpty()) {
|
||||||
|
if (mandatory && !options.isEmpty()) {
|
||||||
|
targets = options;
|
||||||
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
sa.getTargets().add(targetSpellCard);
|
}
|
||||||
|
sa.getTargets().add(ComputerUtilCard.getBestAI(targets));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public static final Player getOpponentFor(final Player player) {
|
|
||||||
// This method is deprecated and currently functions as a synonym for player.getWeakestOpponent
|
|
||||||
// until it can be replaced everywhere in the code.
|
|
||||||
|
|
||||||
// Consider replacing calls to this method either with a multiplayer-friendly determination of
|
|
||||||
// opponent that contextually makes the most sense, or with a direct call to player.getWeakestOpponent
|
|
||||||
// where that is applicable and makes sense from the point of view of multiplayer AI logic.
|
|
||||||
Player opponent = player.getWeakestOpponent();
|
|
||||||
if (opponent != null) {
|
|
||||||
return opponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalStateException("No opponents left ingame for " + player);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int countUsefulCreatures(Player p) {
|
public static int countUsefulCreatures(Player p) {
|
||||||
CardCollection creats = p.getCreaturesInPlay();
|
CardCollection creats = p.getCreaturesInPlay();
|
||||||
int count = 0;
|
int count = 0;
|
||||||
@@ -3047,7 +3010,7 @@ public class ComputerUtil {
|
|||||||
// call this to determine if it's safe to use a life payment spell
|
// call this to determine if it's safe to use a life payment spell
|
||||||
// or trigger "emergency" strategies such as holding mana for Spike Weaver of Counterspell.
|
// or trigger "emergency" strategies such as holding mana for Spike Weaver of Counterspell.
|
||||||
public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) {
|
public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) {
|
||||||
Player opponent = ComputerUtil.getOpponentFor(ai);
|
for (Player opponent: ai.getOpponents()) {
|
||||||
// test whether the human can kill the ai next turn
|
// test whether the human can kill the ai next turn
|
||||||
Combat combat = new Combat(opponent);
|
Combat combat = new Combat(opponent);
|
||||||
boolean containsAttacker = false;
|
boolean containsAttacker = false;
|
||||||
@@ -3073,7 +3036,8 @@ public class ComputerUtil {
|
|||||||
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
|
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -171,8 +171,12 @@ public class ComputerUtilAbility {
|
|||||||
return sa;
|
return sa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Card getAbilitySource(SpellAbility sa) {
|
||||||
|
return sa.getOriginalHost() != null ? sa.getOriginalHost() : sa.getHostCard();
|
||||||
|
}
|
||||||
|
|
||||||
public static String getAbilitySourceName(SpellAbility sa) {
|
public static String getAbilitySourceName(SpellAbility sa) {
|
||||||
final Card c = sa.getOriginalOrHost();
|
final Card c = getAbilitySource(sa);
|
||||||
return c != null ? c.getName() : "";
|
return c != null ? c.getName() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import forge.game.combat.Combat;
|
|||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.cost.CostPayEnergy;
|
import forge.game.cost.CostPayEnergy;
|
||||||
|
import forge.game.cost.CostRemoveCounter;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.keyword.KeywordCollection;
|
import forge.game.keyword.KeywordCollection;
|
||||||
import forge.game.keyword.KeywordInterface;
|
import forge.game.keyword.KeywordInterface;
|
||||||
@@ -76,7 +77,7 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return ComputerUtilCard.getMostExpensivePermanentAI(all);
|
return getMostExpensivePermanentAI(all);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,6 +138,56 @@ public class ComputerUtilCard {
|
|||||||
return Aggregates.itemWithMin(all, CardPredicates.Accessors.fnGetCmc);
|
return Aggregates.itemWithMin(all, CardPredicates.Accessors.fnGetCmc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Card getBestPlaneswalkerToDamage(final List<Card> pws) {
|
||||||
|
Card bestTgt = null;
|
||||||
|
|
||||||
|
// As of right now, ranks planeswalkers by their Current Loyalty * 10 + Big buff if close to "Ultimate"
|
||||||
|
int bestScore = 0;
|
||||||
|
for (Card pw : pws) {
|
||||||
|
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
|
||||||
|
int pwScore = curLoyalty * 10;
|
||||||
|
|
||||||
|
for (SpellAbility sa : pw.getSpellAbilities()) {
|
||||||
|
if (sa.hasParam("Ultimate")) {
|
||||||
|
Integer loyaltyCost = 0;
|
||||||
|
CostRemoveCounter remLoyalty = sa.getPayCosts().getCostPartByType(CostRemoveCounter.class);
|
||||||
|
if (remLoyalty != null) {
|
||||||
|
// if remLoyalty is null, generally there's an AddCounter<0/LOYALTY> cost, like for Gideon Jura.
|
||||||
|
loyaltyCost = remLoyalty.convertAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loyaltyCost != null && loyaltyCost != 0 && loyaltyCost - curLoyalty <= 1) {
|
||||||
|
// Will ultimate soon
|
||||||
|
pwScore += 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pwScore > bestScore) {
|
||||||
|
bestScore = pwScore;
|
||||||
|
bestTgt = pw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestTgt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Card getWorstPlaneswalkerToDamage(final List<Card> pws) {
|
||||||
|
Card bestTgt = null;
|
||||||
|
|
||||||
|
int bestScore = Integer.MAX_VALUE;
|
||||||
|
for (Card pw : pws) {
|
||||||
|
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
|
||||||
|
|
||||||
|
if (curLoyalty < bestScore) {
|
||||||
|
bestScore = curLoyalty;
|
||||||
|
bestTgt = pw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestTgt;
|
||||||
|
}
|
||||||
|
|
||||||
// The AI doesn't really pick the best enchantment, just the most expensive.
|
// The AI doesn't really pick the best enchantment, just the most expensive.
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -217,6 +268,63 @@ public class ComputerUtilCard {
|
|||||||
return Aggregates.random(bLand); // random tapped land of least represented type
|
return Aggregates.random(bLand); // random tapped land of least represented type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* getWorstLand.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param lands
|
||||||
|
* @return a {@link forge.game.card.Card} object.
|
||||||
|
*/
|
||||||
|
public static Card getWorstLand(final List<Card> lands) {
|
||||||
|
Card worstLand = null;
|
||||||
|
int maxScore = Integer.MIN_VALUE;
|
||||||
|
// first, check for tapped, basic lands
|
||||||
|
for (Card tmp : lands) {
|
||||||
|
int score = tmp.isTapped() ? 2 : 0;
|
||||||
|
score += tmp.isBasicLand() ? 1 : 0;
|
||||||
|
score -= tmp.isCreature() ? 4 : 0;
|
||||||
|
for (Card aura : tmp.getEnchantedBy()) {
|
||||||
|
if (aura.getController().isOpponentOf(tmp.getController())) {
|
||||||
|
score += 5;
|
||||||
|
} else {
|
||||||
|
score -= 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (score == maxScore &&
|
||||||
|
CardLists.count(lands, CardPredicates.sharesNameWith(tmp)) > CardLists.count(lands, CardPredicates.sharesNameWith(worstLand))) {
|
||||||
|
worstLand = tmp;
|
||||||
|
}
|
||||||
|
if (score > maxScore) {
|
||||||
|
worstLand = tmp;
|
||||||
|
maxScore = score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return worstLand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Card getBestLandToAnimate(final Iterable<Card> lands) {
|
||||||
|
Card land = null;
|
||||||
|
int maxScore = Integer.MIN_VALUE;
|
||||||
|
// first, check for tapped, basic lands
|
||||||
|
for (Card tmp : lands) {
|
||||||
|
int score = tmp.isTapped() ? 0 : 2;
|
||||||
|
score += tmp.isBasicLand() ? 2 : 0;
|
||||||
|
score -= tmp.isCreature() ? 4 : 0;
|
||||||
|
score -= 5 * tmp.getEnchantedBy().size();
|
||||||
|
|
||||||
|
if (score == maxScore &&
|
||||||
|
CardLists.count(lands, CardPredicates.sharesNameWith(tmp)) > CardLists.count(lands, CardPredicates.sharesNameWith(land))) {
|
||||||
|
land = tmp;
|
||||||
|
}
|
||||||
|
if (score > maxScore) {
|
||||||
|
land = tmp;
|
||||||
|
maxScore = score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return land;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* getCheapestPermanentAI.
|
* getCheapestPermanentAI.
|
||||||
@@ -265,16 +373,14 @@ public class ComputerUtilCard {
|
|||||||
* @return a {@link forge.game.card.Card} object.
|
* @return a {@link forge.game.card.Card} object.
|
||||||
*/
|
*/
|
||||||
public static Card getBestAI(final Iterable<Card> list) {
|
public static Card getBestAI(final Iterable<Card> list) {
|
||||||
// Get Best will filter by appropriate getBest list if ALL of the list
|
// Get Best will filter by appropriate getBest list if ALL of the list is of that type
|
||||||
// is of that type
|
|
||||||
if (Iterables.all(list, CardPredicates.Presets.CREATURES)) {
|
if (Iterables.all(list, CardPredicates.Presets.CREATURES)) {
|
||||||
return ComputerUtilCard.getBestCreatureAI(list);
|
return ComputerUtilCard.getBestCreatureAI(list);
|
||||||
}
|
}
|
||||||
if (Iterables.all(list, CardPredicates.Presets.LANDS)) {
|
if (Iterables.all(list, CardPredicates.Presets.LANDS)) {
|
||||||
return getBestLandAI(list);
|
return getBestLandAI(list);
|
||||||
}
|
}
|
||||||
// TODO - Once we get an EvaluatePermanent this should call
|
// TODO - Once we get an EvaluatePermanent this should call getBestPermanent()
|
||||||
// getBestPermanent()
|
|
||||||
return ComputerUtilCard.getMostExpensivePermanentAI(list);
|
return ComputerUtilCard.getMostExpensivePermanentAI(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,7 +489,7 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (biasLand && Iterables.any(list, CardPredicates.Presets.LANDS)) {
|
if (biasLand && Iterables.any(list, CardPredicates.Presets.LANDS)) {
|
||||||
return ComputerUtilCard.getWorstLand(CardLists.filter(list, CardPredicates.Presets.LANDS));
|
return getWorstLand(CardLists.filter(list, CardPredicates.Presets.LANDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean hasCreatures = Iterables.any(list, CardPredicates.Presets.CREATURES);
|
final boolean hasCreatures = Iterables.any(list, CardPredicates.Presets.CREATURES);
|
||||||
@@ -393,7 +499,7 @@ public class ComputerUtilCard {
|
|||||||
|
|
||||||
List<Card> lands = CardLists.filter(list, CardPredicates.Presets.LANDS);
|
List<Card> lands = CardLists.filter(list, CardPredicates.Presets.LANDS);
|
||||||
if (lands.size() > 6) {
|
if (lands.size() > 6) {
|
||||||
return ComputerUtilCard.getWorstLand(lands);
|
return getWorstLand(lands);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasEnchantmants || hasArtifacts) {
|
if (hasEnchantmants || hasArtifacts) {
|
||||||
@@ -410,8 +516,7 @@ public class ComputerUtilCard {
|
|||||||
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
|
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Planeswalkers fall through to here, lands will fall through if there
|
// Planeswalkers fall through to here, lands will fall through if there aren't very many
|
||||||
// aren't very many
|
|
||||||
return getCheapestPermanentAI(list, null, false);
|
return getCheapestPermanentAI(list, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,7 +549,7 @@ public class ComputerUtilCard {
|
|||||||
public static final Comparator<Card> EvaluateCreatureComparator = new Comparator<Card>() {
|
public static final Comparator<Card> EvaluateCreatureComparator = new Comparator<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(final Card a, final Card b) {
|
public int compare(final Card a, final Card b) {
|
||||||
return ComputerUtilCard.evaluateCreature(b) - ComputerUtilCard.evaluateCreature(a);
|
return evaluateCreature(b) - evaluateCreature(a);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -550,7 +655,7 @@ public class ComputerUtilCard {
|
|||||||
*/
|
*/
|
||||||
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
|
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
|
||||||
AiBlockController aiBlk = new AiBlockController(ai);
|
AiBlockController aiBlk = new AiBlockController(ai);
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
Combat combat = new Combat(opp);
|
Combat combat = new Combat(opp);
|
||||||
//Use actual attackers if available, else consider all possible attackers
|
//Use actual attackers if available, else consider all possible attackers
|
||||||
Combat currentCombat = ai.getGame().getCombat();
|
Combat currentCombat = ai.getGame().getCombat();
|
||||||
@@ -611,6 +716,7 @@ public class ComputerUtilCard {
|
|||||||
|
|
||||||
int bigCMC = -1;
|
int bigCMC = -1;
|
||||||
for (final Card card : all) {
|
for (final Card card : all) {
|
||||||
|
// TODO when PlayAi can consider MDFC this should also look at the back face (if not on stack or battlefield)
|
||||||
int curCMC = card.getCMC();
|
int curCMC = card.getCMC();
|
||||||
|
|
||||||
// Add all cost of all auras with the same controller
|
// Add all cost of all auras with the same controller
|
||||||
@@ -828,57 +934,6 @@ public class ComputerUtilCard {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* getWorstLand.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param lands
|
|
||||||
* @return a {@link forge.game.card.Card} object.
|
|
||||||
*/
|
|
||||||
public static Card getWorstLand(final List<Card> lands) {
|
|
||||||
Card worstLand = null;
|
|
||||||
int maxScore = Integer.MIN_VALUE;
|
|
||||||
// first, check for tapped, basic lands
|
|
||||||
for (Card tmp : lands) {
|
|
||||||
int score = tmp.isTapped() ? 2 : 0;
|
|
||||||
score += tmp.isBasicLand() ? 1 : 0;
|
|
||||||
score -= tmp.isCreature() ? 4 : 0;
|
|
||||||
for (Card aura : tmp.getEnchantedBy()) {
|
|
||||||
if (aura.getController().isOpponentOf(tmp.getController())) {
|
|
||||||
score += 5;
|
|
||||||
} else {
|
|
||||||
score -= 5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (score >= maxScore) {
|
|
||||||
worstLand = tmp;
|
|
||||||
maxScore = score;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return worstLand;
|
|
||||||
} // end getWorstLand
|
|
||||||
|
|
||||||
public static Card getBestLandToAnimate(final Iterable<Card> lands) {
|
|
||||||
Card land = null;
|
|
||||||
int maxScore = Integer.MIN_VALUE;
|
|
||||||
// first, check for tapped, basic lands
|
|
||||||
for (Card tmp : lands) {
|
|
||||||
// TODO Improve this by choosing basic lands that I have plenty of mana in
|
|
||||||
int score = tmp.isTapped() ? 0 : 2;
|
|
||||||
score += tmp.isBasicLand() ? 2 : 0;
|
|
||||||
score -= tmp.isCreature() ? 4 : 0;
|
|
||||||
score -= 5 * tmp.getEnchantedBy().size();
|
|
||||||
|
|
||||||
if (score >= maxScore) {
|
|
||||||
land = tmp;
|
|
||||||
maxScore = score;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return land;
|
|
||||||
} // end getBestLandToAnimate
|
|
||||||
|
|
||||||
public static final Predicate<Deck> AI_KNOWS_HOW_TO_PLAY_ALL_CARDS = new Predicate<Deck>() {
|
public static final Predicate<Deck> AI_KNOWS_HOW_TO_PLAY_ALL_CARDS = new Predicate<Deck>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Deck d) {
|
public boolean apply(Deck d) {
|
||||||
@@ -891,6 +946,7 @@ public class ComputerUtilCard {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static List<String> chooseColor(SpellAbility sa, int min, int max, List<String> colorChoices) {
|
public static List<String> chooseColor(SpellAbility sa, int min, int max, List<String> colorChoices) {
|
||||||
List<String> chosen = new ArrayList<>();
|
List<String> chosen = new ArrayList<>();
|
||||||
Player ai = sa.getActivatingPlayer();
|
Player ai = sa.getActivatingPlayer();
|
||||||
@@ -1595,7 +1651,7 @@ public class ComputerUtilCard {
|
|||||||
*/
|
*/
|
||||||
public static Card getPumpedCreature(final Player ai, final SpellAbility sa,
|
public static Card getPumpedCreature(final Player ai, final SpellAbility sa,
|
||||||
final Card c, int toughness, int power, final List<String> keywords) {
|
final Card c, int toughness, int power, final List<String> keywords) {
|
||||||
Card pumped = CardFactory.copyCard(c, true);
|
Card pumped = CardFactory.copyCard(c, false);
|
||||||
pumped.setSickness(c.hasSickness());
|
pumped.setSickness(c.hasSickness());
|
||||||
final long timestamp = c.getGame().getNextTimestamp();
|
final long timestamp = c.getGame().getNextTimestamp();
|
||||||
final List<String> kws = new ArrayList<>();
|
final List<String> kws = new ArrayList<>();
|
||||||
@@ -1633,7 +1689,7 @@ public class ComputerUtilCard {
|
|||||||
pumped.addChangedCardKeywords(kws, null, false, false, timestamp);
|
pumped.addChangedCardKeywords(kws, null, false, false, timestamp);
|
||||||
Set<CounterType> types = c.getCounters().keySet();
|
Set<CounterType> types = c.getCounters().keySet();
|
||||||
for(CounterType ct : types) {
|
for(CounterType ct : types) {
|
||||||
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, true, null);
|
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, sa, true, null);
|
||||||
}
|
}
|
||||||
//Copies tap-state and extra keywords (auras, equipment, etc.)
|
//Copies tap-state and extra keywords (auras, equipment, etc.)
|
||||||
if (c.isTapped()) {
|
if (c.isTapped()) {
|
||||||
@@ -1644,11 +1700,12 @@ public class ComputerUtilCard {
|
|||||||
copiedKeywords.insertAll(pumped.getKeywords());
|
copiedKeywords.insertAll(pumped.getKeywords());
|
||||||
List<KeywordInterface> toCopy = Lists.newArrayList();
|
List<KeywordInterface> toCopy = Lists.newArrayList();
|
||||||
for (KeywordInterface k : c.getKeywords()) {
|
for (KeywordInterface k : c.getKeywords()) {
|
||||||
if (!copiedKeywords.contains(k.getOriginal())) {
|
KeywordInterface copiedKI = k.copy(c, true);
|
||||||
if (k.getHidden()) {
|
if (!copiedKeywords.contains(copiedKI.getOriginal())) {
|
||||||
pumped.addHiddenExtrinsicKeyword(k);
|
if (copiedKI.getHidden()) {
|
||||||
|
pumped.addHiddenExtrinsicKeyword(copiedKI);
|
||||||
} else {
|
} else {
|
||||||
toCopy.add(k);
|
toCopy.add(copiedKI);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1678,37 +1735,27 @@ public class ComputerUtilCard {
|
|||||||
// remove old boost that might be copied
|
// remove old boost that might be copied
|
||||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||||
vCard.removePTBoost(c.getTimestamp(), stAb.getId());
|
vCard.removePTBoost(c.getTimestamp(), stAb.getId());
|
||||||
final Map<String, String> params = stAb.getMapParams();
|
if (!stAb.getParam("Mode").equals("Continuous")) {
|
||||||
if (!params.get("Mode").equals("Continuous")) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!params.containsKey("Affected")) {
|
if (!stAb.hasParam("Affected")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!params.containsKey("AddPower") && !params.containsKey("AddToughness")) {
|
if (!stAb.hasParam("AddPower") && !stAb.hasParam("AddToughness")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final String valid = params.get("Affected");
|
if (!vCard.isValid(stAb.getParam("Affected").split(","), c.getController(), c, stAb)) {
|
||||||
if (!vCard.isValid(valid, c.getController(), c, null)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int att = 0;
|
int att = 0;
|
||||||
if (params.containsKey("AddPower")) {
|
if (stAb.hasParam("AddPower")) {
|
||||||
String addP = params.get("AddPower");
|
String addP = stAb.getParam("AddPower");
|
||||||
if (addP.equals("AffectedX")) {
|
att = AbilityUtils.calculateAmount(addP.startsWith("Affected") ? vCard : c, addP, stAb, true);
|
||||||
att = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addP));
|
|
||||||
} else {
|
|
||||||
att = AbilityUtils.calculateAmount(c, addP, stAb);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
int def = 0;
|
int def = 0;
|
||||||
if (params.containsKey("AddToughness")) {
|
if (stAb.hasParam("AddToughness")) {
|
||||||
String addT = params.get("AddToughness");
|
String addT = stAb.getParam("AddToughness");
|
||||||
if (addT.equals("AffectedY")) {
|
def = AbilityUtils.calculateAmount(addT.startsWith("Affected") ? vCard : c, addT, stAb, true);
|
||||||
def = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addT));
|
|
||||||
} else {
|
|
||||||
def = AbilityUtils.calculateAmount(c, addT, stAb);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
vCard.addPTBoost(att, def, c.getTimestamp(), stAb.getId());
|
vCard.addPTBoost(att, def, c.getTimestamp(), stAb.getId());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import forge.game.ability.ApiType;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardFactoryUtil;
|
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
@@ -97,34 +96,39 @@ public class ComputerUtilCombat {
|
|||||||
* canAttackNextTurn.
|
* canAttackNextTurn.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param atacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param defender
|
||||||
* the defending {@link GameEntity}.
|
* the defending {@link GameEntity}.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean canAttackNextTurn(final Card atacker, final GameEntity defender) {
|
public static boolean canAttackNextTurn(final Card attacker, final GameEntity defender) {
|
||||||
if (!atacker.isCreature()) {
|
if (!attacker.isCreature()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!CombatUtil.canAttackNextTurn(atacker, defender)) {
|
if (!CombatUtil.canAttackNextTurn(attacker, defender)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final KeywordInterface inst : atacker.getKeywords()) {
|
for (final KeywordInterface inst : attacker.getKeywords()) {
|
||||||
final String keyword = inst.getOriginal();
|
final String keyword = inst.getOriginal();
|
||||||
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
|
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
|
||||||
final String defined = keyword.split(":")[1];
|
final String defined = keyword.split(":")[1];
|
||||||
final Player player = AbilityUtils.getDefinedPlayers(atacker, defined, null).get(0);
|
final Player player = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0);
|
||||||
if (!defender.equals(player)) {
|
if (!defender.equals(player)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO this should be a factor but needs some alignment with AttachAi
|
||||||
|
//boolean leavesPlay = !ComputerUtilCard.hasActiveUndyingOrPersist(attacker)
|
||||||
|
// && ((attacker.hasKeyword(Keyword.VANISHING) && attacker.getCounters(CounterEnumType.TIME) == 1)
|
||||||
|
// || (attacker.hasKeyword(Keyword.FADING) && attacker.getCounters(CounterEnumType.FADE) == 0)
|
||||||
|
// || attacker.hasSVar("EndOfTurnLeavePlay"));
|
||||||
// The creature won't untap next turn
|
// The creature won't untap next turn
|
||||||
return !atacker.isTapped() || Untap.canUntap(atacker);
|
return !attacker.isTapped() || Untap.canUntap(attacker);
|
||||||
} // canAttackNextTurn(Card, GameEntity)
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -287,14 +291,12 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int lifeThatWouldRemain(final Player ai, final Combat combat) {
|
public static int lifeThatWouldRemain(final Player ai, final Combat combat) {
|
||||||
|
|
||||||
int damage = 0;
|
int damage = 0;
|
||||||
|
|
||||||
final List<Card> attackers = combat.getAttackersOf(ai);
|
final List<Card> attackers = combat.getAttackersOf(ai);
|
||||||
final List<Card> unblocked = Lists.newArrayList();
|
final List<Card> unblocked = Lists.newArrayList();
|
||||||
|
|
||||||
for (final Card attacker : attackers) {
|
for (final Card attacker : attackers) {
|
||||||
|
|
||||||
final List<Card> blockers = combat.getBlockers(attacker);
|
final List<Card> blockers = combat.getBlockers(attacker);
|
||||||
|
|
||||||
if ((blockers.size() == 0)
|
if ((blockers.size() == 0)
|
||||||
@@ -330,7 +332,7 @@ public class ComputerUtilCombat {
|
|||||||
*/
|
*/
|
||||||
public static int resultingPoison(final Player ai, final Combat combat) {
|
public static int resultingPoison(final Player ai, final Combat combat) {
|
||||||
|
|
||||||
// ai can't get poision counters, so the value can't change
|
// ai can't get poison counters, so the value can't change
|
||||||
if (!ai.canReceiveCounters(CounterEnumType.POISON)) {
|
if (!ai.canReceiveCounters(CounterEnumType.POISON)) {
|
||||||
return ai.getPoisonCounters();
|
return ai.getPoisonCounters();
|
||||||
}
|
}
|
||||||
@@ -408,14 +410,12 @@ public class ComputerUtilCombat {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// check for creatures that must be blocked
|
// check for creatures that must be blocked
|
||||||
final List<Card> attackers = combat.getAttackersOf(ai);
|
final List<Card> attackers = combat.getAttackersOf(ai);
|
||||||
|
|
||||||
final List<Card> threateningCommanders = getLifeThreateningCommanders(ai,combat);
|
final List<Card> threateningCommanders = getLifeThreateningCommanders(ai,combat);
|
||||||
|
|
||||||
for (final Card attacker : attackers) {
|
for (final Card attacker : attackers) {
|
||||||
|
|
||||||
final List<Card> blockers = combat.getBlockers(attacker);
|
final List<Card> blockers = combat.getBlockers(attacker);
|
||||||
|
|
||||||
if (blockers.isEmpty()) {
|
if (blockers.isEmpty()) {
|
||||||
@@ -469,7 +469,6 @@ public class ComputerUtilCombat {
|
|||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean wouldLoseLife(final Player ai, final Combat combat) {
|
public static boolean wouldLoseLife(final Player ai, final Combat combat) {
|
||||||
|
|
||||||
return (ComputerUtilCombat.lifeThatWouldRemain(ai, combat) < ai.getLife());
|
return (ComputerUtilCombat.lifeThatWouldRemain(ai, combat) < ai.getLife());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,8 +487,7 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean lifeInSeriousDanger(final Player ai, final Combat combat, final int payment) {
|
public static boolean lifeInSeriousDanger(final Player ai, final Combat combat, final int payment) {
|
||||||
// life in danger only cares about the player's life. Not about a
|
// life in danger only cares about the player's life. Not about a Planeswalkers life
|
||||||
// Planeswalkers life
|
|
||||||
if (ai.cantLose() || combat == null) {
|
if (ai.cantLose() || combat == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -564,8 +562,7 @@ public class ComputerUtilCombat {
|
|||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This calculates the amount of damage a blocker in a blockgang can deal to
|
// This calculates the amount of damage a blocker in a blockgang can deal to the attacker
|
||||||
// the attacker
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* dealsDamageAsBlocker.
|
* dealsDamageAsBlocker.
|
||||||
@@ -578,7 +575,6 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int dealsDamageAsBlocker(final Card attacker, final Card defender) {
|
public static int dealsDamageAsBlocker(final Card attacker, final Card defender) {
|
||||||
|
|
||||||
int defenderDamage = predictDamageByBlockerWithoutDoubleStrike(attacker, defender);
|
int defenderDamage = predictDamageByBlockerWithoutDoubleStrike(attacker, defender);
|
||||||
|
|
||||||
if (defender.hasKeyword(Keyword.DOUBLE_STRIKE)) {
|
if (defender.hasKeyword(Keyword.DOUBLE_STRIKE)) {
|
||||||
@@ -643,7 +639,6 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int totalShieldDamage(final Card attacker, final List<Card> defenders) {
|
public static int totalShieldDamage(final Card attacker, final List<Card> defenders) {
|
||||||
|
|
||||||
int defenderDefense = 0;
|
int defenderDefense = 0;
|
||||||
|
|
||||||
for (final Card defender : defenders) {
|
for (final Card defender : defenders) {
|
||||||
@@ -667,7 +662,6 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public static int shieldDamage(final Card attacker, final Card blocker) {
|
public static int shieldDamage(final Card attacker, final Card blocker) {
|
||||||
|
|
||||||
if (ComputerUtilCombat.canDestroyBlockerBeforeFirstStrike(blocker, attacker, false)) {
|
if (ComputerUtilCombat.canDestroyBlockerBeforeFirstStrike(blocker, attacker, false)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -772,7 +766,6 @@ public class ComputerUtilCombat {
|
|||||||
public static boolean combatTriggerWillTrigger(final Card attacker, final Card defender, final Trigger trigger,
|
public static boolean combatTriggerWillTrigger(final Card attacker, final Card defender, final Trigger trigger,
|
||||||
Combat combat, final List<Card> plannedAttackers) {
|
Combat combat, final List<Card> plannedAttackers) {
|
||||||
final Game game = attacker.getGame();
|
final Game game = attacker.getGame();
|
||||||
final Map<String, String> trigParams = trigger.getMapParams();
|
|
||||||
boolean willTrigger = false;
|
boolean willTrigger = false;
|
||||||
final Card source = trigger.getHostCard();
|
final Card source = trigger.getHostCard();
|
||||||
if (combat == null) {
|
if (combat == null) {
|
||||||
@@ -795,29 +788,27 @@ public class ComputerUtilCombat {
|
|||||||
if (combat.isAttacking(attacker)) {
|
if (combat.isAttacking(attacker)) {
|
||||||
return false; // The trigger should have triggered already
|
return false; // The trigger should have triggered already
|
||||||
}
|
}
|
||||||
if (trigParams.containsKey("ValidCard")) {
|
if (trigger.hasParam("ValidCard")) {
|
||||||
if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","))
|
if (!trigger.matchesValidParam("ValidCard", attacker)
|
||||||
&& !(combat.isAttacking(source) && trigger.matchesValid(source,
|
&& !(combat.isAttacking(source) && trigger.matchesValidParam("ValidCard", source)
|
||||||
trigParams.get("ValidCard").split(","))
|
&& !trigger.hasParam("Alone"))) {
|
||||||
&& !trigParams.containsKey("Alone"))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (trigParams.containsKey("Attacked")) {
|
if (trigger.hasParam("Attacked")) {
|
||||||
if (combat.isAttacking(attacker)) {
|
if (combat.isAttacking(attacker)) {
|
||||||
GameEntity attacked = combat.getDefenderByAttacker(attacker);
|
if (!trigger.matchesValidParam("Attacked", combat.getDefenderByAttacker(attacker))) {
|
||||||
if (!trigger.matchesValid(attacked, trigParams.get("Attacked").split(","))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ("You,Planeswalker.YouCtrl".equals(trigParams.get("Attacked"))) {
|
if ("You,Planeswalker.YouCtrl".equals(trigger.getParam("Attacked"))) {
|
||||||
if (source.getController() == attacker.getController()) {
|
if (source.getController() == attacker.getController()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (trigParams.containsKey("Alone") && plannedAttackers != null && plannedAttackers.size() != 1) {
|
if (trigger.hasParam("Alone") && plannedAttackers != null && plannedAttackers.size() != 1) {
|
||||||
return false; // won't trigger since the AI is planning to attack with more than one creature
|
return false; // won't trigger since the AI is planning to attack with more than one creature
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -825,12 +816,10 @@ public class ComputerUtilCombat {
|
|||||||
// defender == null means unblocked
|
// defender == null means unblocked
|
||||||
if ((defender == null) && mode == TriggerType.AttackerUnblocked) {
|
if ((defender == null) && mode == TriggerType.AttackerUnblocked) {
|
||||||
willTrigger = true;
|
willTrigger = true;
|
||||||
if (trigParams.containsKey("ValidCard")) {
|
if (!trigger.matchesValidParam("ValidCard", attacker)) {
|
||||||
if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (defender == null) {
|
if (defender == null) {
|
||||||
return willTrigger;
|
return willTrigger;
|
||||||
@@ -838,8 +827,8 @@ public class ComputerUtilCombat {
|
|||||||
|
|
||||||
if (mode == TriggerType.Blocks) {
|
if (mode == TriggerType.Blocks) {
|
||||||
willTrigger = true;
|
willTrigger = true;
|
||||||
if (trigParams.containsKey("ValidBlocked")) {
|
if (trigger.hasParam("ValidBlocked")) {
|
||||||
String validBlocked = trigParams.get("ValidBlocked");
|
String validBlocked = trigger.getParam("ValidBlocked");
|
||||||
if (validBlocked.contains(".withLesserPower")) {
|
if (validBlocked.contains(".withLesserPower")) {
|
||||||
// Have to check this restriction here as triggering objects aren't set yet, so
|
// Have to check this restriction here as triggering objects aren't set yet, so
|
||||||
// ValidBlocked$Creature.powerLTX where X:TriggeredBlocker$CardPower crashes with NPE
|
// ValidBlocked$Creature.powerLTX where X:TriggeredBlocker$CardPower crashes with NPE
|
||||||
@@ -852,8 +841,8 @@ public class ComputerUtilCombat {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (trigParams.containsKey("ValidCard")) {
|
if (trigger.hasParam("ValidCard")) {
|
||||||
String validBlocker = trigParams.get("ValidCard");
|
String validBlocker = trigger.getParam("ValidCard");
|
||||||
if (validBlocker.contains(".withLesserPower")) {
|
if (validBlocker.contains(".withLesserPower")) {
|
||||||
// Have to check this restriction here as triggering objects aren't set yet, so
|
// Have to check this restriction here as triggering objects aren't set yet, so
|
||||||
// ValidCard$Creature.powerLTX where X:TriggeredAttacker$CardPower crashes with NPE
|
// ValidCard$Creature.powerLTX where X:TriggeredAttacker$CardPower crashes with NPE
|
||||||
@@ -868,29 +857,23 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
} else if (mode == TriggerType.AttackerBlocked || mode == TriggerType.AttackerBlockedByCreature) {
|
} else if (mode == TriggerType.AttackerBlocked || mode == TriggerType.AttackerBlockedByCreature) {
|
||||||
willTrigger = true;
|
willTrigger = true;
|
||||||
if (trigParams.containsKey("ValidBlocker")) {
|
if (!trigger.matchesValidParam("ValidBlocker", defender)) {
|
||||||
if (!trigger.matchesValid(defender, trigParams.get("ValidBlocker").split(","))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
if (!trigger.matchesValidParam("ValidCard", attacker)) {
|
||||||
if (trigParams.containsKey("ValidCard")) {
|
|
||||||
if (!trigger.matchesValid(attacker, trigParams.get("ValidCard").split(","))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if (mode == TriggerType.DamageDone) {
|
} else if (mode == TriggerType.DamageDone) {
|
||||||
willTrigger = true;
|
willTrigger = true;
|
||||||
if (trigParams.containsKey("ValidSource")) {
|
if (trigger.hasParam("ValidSource")) {
|
||||||
if (!(trigger.matchesValid(defender, trigParams.get("ValidSource").split(","))
|
if (!(trigger.matchesValidParam("ValidSource", defender)
|
||||||
&& defender.getNetCombatDamage() > 0
|
&& defender.getNetCombatDamage() > 0
|
||||||
&& (!trigParams.containsKey("ValidTarget")
|
&& trigger.matchesValidParam("ValidTarget", attacker))) {
|
||||||
|| trigger.matchesValid(attacker, trigParams.get("ValidTarget").split(","))))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!(trigger.matchesValid(attacker, trigParams.get("ValidSource").split(","))
|
if (!(trigger.matchesValidParam("ValidSource", attacker)
|
||||||
&& attacker.getNetCombatDamage() > 0
|
&& attacker.getNetCombatDamage() > 0
|
||||||
&& (!trigParams.containsKey("ValidTarget")
|
&& trigger.matchesValidParam("ValidTarget", defender))) {
|
||||||
|| trigger.matchesValid(defender, trigParams.get("ValidTarget").split(","))))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -939,30 +922,22 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final Game game = attacker.getGame();
|
final Game game = attacker.getGame();
|
||||||
// look out for continuous static abilities that only care for blocking
|
// look out for continuous static abilities that only care for blocking creatures
|
||||||
// creatures
|
|
||||||
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
||||||
for (final Card card : cardList) {
|
for (final Card card : cardList) {
|
||||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||||
final Map<String, String> params = stAb.getMapParams();
|
if (!stAb.getParam("Mode").equals("Continuous")) {
|
||||||
if (!params.get("Mode").equals("Continuous")) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!params.containsKey("Affected") || !params.get("Affected").contains("blocking")) {
|
if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("blocking")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final String valid = TextUtil.fastReplace(params.get("Affected"), "blocking", "Creature");
|
final String valid = TextUtil.fastReplace(stAb.getParam("Affected"), "blocking", "Creature");
|
||||||
if (!blocker.isValid(valid, card.getController(), card, null)) {
|
if (!blocker.isValid(valid, card.getController(), card, stAb)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("AddPower")) {
|
if (stAb.hasParam("AddPower")) {
|
||||||
if (params.get("AddPower").equals("X")) {
|
power += AbilityUtils.calculateAmount(card, stAb.getParam("AddPower"), stAb);
|
||||||
power += CardFactoryUtil.xCount(card, card.getSVar("X"));
|
|
||||||
} else if (params.get("AddPower").equals("Y")) {
|
|
||||||
power += CardFactoryUtil.xCount(card, card.getSVar("Y"));
|
|
||||||
} else {
|
|
||||||
power += Integer.valueOf(params.get("AddPower"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1247,31 +1222,23 @@ public class ComputerUtilCombat {
|
|||||||
theTriggers.addAll(blocker.getTriggers());
|
theTriggers.addAll(blocker.getTriggers());
|
||||||
}
|
}
|
||||||
|
|
||||||
// look out for continuous static abilities that only care for attacking
|
// look out for continuous static abilities that only care for attacking creatures
|
||||||
// creatures
|
|
||||||
if (!withoutCombatStaticAbilities) {
|
if (!withoutCombatStaticAbilities) {
|
||||||
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
||||||
for (final Card card : cardList) {
|
for (final Card card : cardList) {
|
||||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||||
final Map<String, String> params = stAb.getMapParams();
|
if (!stAb.getParam("Mode").equals("Continuous")) {
|
||||||
if (!params.get("Mode").equals("Continuous")) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!params.containsKey("Affected") || !params.get("Affected").contains("attacking")) {
|
if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("attacking")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final String valid = TextUtil.fastReplace(params.get("Affected"), "attacking", "Creature");
|
final String valid = TextUtil.fastReplace(stAb.getParam("Affected"), "attacking", "Creature");
|
||||||
if (!attacker.isValid(valid, card.getController(), card, null)) {
|
if (!attacker.isValid(valid, card.getController(), card, stAb)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("AddPower")) {
|
if (stAb.hasParam("AddPower")) {
|
||||||
if (params.get("AddPower").equals("X")) {
|
power += AbilityUtils.calculateAmount(card, stAb.getParam("AddPower"), stAb);
|
||||||
power += CardFactoryUtil.xCount(card, card.getSVar("X"));
|
|
||||||
} else if (params.get("AddPower").equals("Y")) {
|
|
||||||
power += CardFactoryUtil.xCount(card, card.getSVar("Y"));
|
|
||||||
} else {
|
|
||||||
power += Integer.valueOf(params.get("AddPower"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1345,7 +1312,7 @@ public class ComputerUtilCombat {
|
|||||||
} else if (bonus.contains("TriggeredAttacker$CardToughness")) {
|
} else if (bonus.contains("TriggeredAttacker$CardToughness")) {
|
||||||
bonus = TextUtil.fastReplace(bonus, "TriggeredAttacker$CardToughness", TextUtil.concatNoSpace("Number$", String.valueOf(attacker.getNetToughness())));
|
bonus = TextUtil.fastReplace(bonus, "TriggeredAttacker$CardToughness", TextUtil.concatNoSpace("Number$", String.valueOf(attacker.getNetToughness())));
|
||||||
}
|
}
|
||||||
power += CardFactoryUtil.xCount(source, bonus);
|
power += AbilityUtils.calculateAmount(source, bonus, sa);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1442,8 +1409,7 @@ public class ComputerUtilCombat {
|
|||||||
theTriggers.addAll(blocker.getTriggers());
|
theTriggers.addAll(blocker.getTriggers());
|
||||||
}
|
}
|
||||||
|
|
||||||
// look out for continuous static abilities that only care for attacking
|
// look out for continuous static abilities that only care for attacking creatures
|
||||||
// creatures
|
|
||||||
if (!withoutCombatStaticAbilities) {
|
if (!withoutCombatStaticAbilities) {
|
||||||
final CardCollectionView cardList = game.getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView cardList = game.getCardsIn(ZoneType.Battlefield);
|
||||||
for (final Card card : cardList) {
|
for (final Card card : cardList) {
|
||||||
@@ -1535,7 +1501,7 @@ public class ComputerUtilCombat {
|
|||||||
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
||||||
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
|
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
|
||||||
}
|
}
|
||||||
toughness += CardFactoryUtil.xCount(source, bonus);
|
toughness += AbilityUtils.calculateAmount(source, bonus, sa);
|
||||||
}
|
}
|
||||||
} else if (ApiType.PumpAll.equals(sa.getApi())) {
|
} else if (ApiType.PumpAll.equals(sa.getApi())) {
|
||||||
|
|
||||||
@@ -1568,7 +1534,7 @@ public class ComputerUtilCombat {
|
|||||||
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
||||||
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
|
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
|
||||||
}
|
}
|
||||||
toughness += CardFactoryUtil.xCount(source, bonus);
|
toughness += AbilityUtils.calculateAmount(source, bonus, sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2251,11 +2217,12 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public final static int getDamageToKill(final Card c) {
|
public final static int getDamageToKill(final Card c) {
|
||||||
int killDamage = c.getLethalDamage() + c.getPreventNextDamageTotalShields();
|
int damageShield = c.getPreventNextDamageTotalShields();
|
||||||
|
int killDamage = c.getLethalDamage() + damageShield;
|
||||||
|
|
||||||
if ((killDamage > c.getPreventNextDamageTotalShields())
|
if ((killDamage > damageShield)
|
||||||
&& c.hasSVar("DestroyWhenDamaged")) {
|
&& c.hasSVar("DestroyWhenDamaged")) {
|
||||||
killDamage = 1 + c.getPreventNextDamageTotalShields();
|
killDamage = 1 + damageShield;
|
||||||
}
|
}
|
||||||
|
|
||||||
return killDamage;
|
return killDamage;
|
||||||
@@ -2277,7 +2244,6 @@ public class ComputerUtilCombat {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public final static int predictDamageTo(final Player target, final int damage, final Card source, final boolean isCombat) {
|
public final static int predictDamageTo(final Player target, final int damage, final Card source, final boolean isCombat) {
|
||||||
|
|
||||||
final Game game = target.getGame();
|
final Game game = target.getGame();
|
||||||
int restDamage = damage;
|
int restDamage = damage;
|
||||||
|
|
||||||
@@ -2286,39 +2252,38 @@ public class ComputerUtilCombat {
|
|||||||
// Predict replacement effects
|
// Predict replacement effects
|
||||||
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||||
for (final ReplacementEffect re : ca.getReplacementEffects()) {
|
for (final ReplacementEffect re : ca.getReplacementEffects()) {
|
||||||
Map<String, String> params = re.getMapParams();
|
if (!re.getMode().equals(ReplacementType.DamageDone) ||
|
||||||
if (!re.getMode().equals(ReplacementType.DamageDone) || !params.containsKey("PreventionEffect")) {
|
(!re.hasParam("PreventionEffect") && !re.hasParam("Prevent"))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Immortal Coil prevents the damage but has a similar negative effect
|
// Immortal Coil prevents the damage but has a similar negative effect
|
||||||
if ("Immortal Coil".equals(ca.getName())) {
|
if ("Immortal Coil".equals(ca.getName())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("ValidSource")
|
if (!re.matchesValidParam("ValidSource", source)) {
|
||||||
&& !source.isValid(params.get("ValidSource"), ca.getController(), ca, null)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("ValidTarget")
|
if (!re.matchesValidParam("ValidTarget", target)) {
|
||||||
&& !target.isValid(params.get("ValidTarget"), ca.getController(), ca, null)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (params.containsKey("IsCombat")) {
|
if (re.hasParam("IsCombat")) {
|
||||||
if (params.get("IsCombat").equals("True")) {
|
if (re.getParam("IsCombat").equals("True") != isCombat) {
|
||||||
if (!isCombat) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isCombat) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (re.hasParam("Prevent")) {
|
||||||
|
return 0;
|
||||||
|
} else if (re.getOverridingAbility() != null) {
|
||||||
|
SpellAbility repSA = re.getOverridingAbility();
|
||||||
|
if (repSA.getApi() == ApiType.ReplaceDamage) {
|
||||||
|
return Math.max(0, restDamage - AbilityUtils.calculateAmount(ca, repSA.getParam("Amount"), repSA));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
restDamage = target.staticDamagePrevention(restDamage, source, isCombat, true);
|
restDamage = target.staticDamagePrevention(restDamage, 0, source, isCombat);
|
||||||
|
|
||||||
return restDamage;
|
return restDamage;
|
||||||
}
|
}
|
||||||
@@ -2339,13 +2304,7 @@ public class ComputerUtilCombat {
|
|||||||
// This function helps the AI calculate the actual amount of damage an
|
// This function helps the AI calculate the actual amount of damage an
|
||||||
// effect would deal
|
// effect would deal
|
||||||
public final static int predictDamageTo(final Card target, final int damage, final Card source, final boolean isCombat) {
|
public final static int predictDamageTo(final Card target, final int damage, final Card source, final boolean isCombat) {
|
||||||
|
return predictDamageTo(target, damage, 0, source, isCombat);
|
||||||
int restDamage = damage;
|
|
||||||
|
|
||||||
restDamage = target.staticReplaceDamage(restDamage, source, isCombat);
|
|
||||||
restDamage = target.staticDamagePrevention(restDamage, source, isCombat, true);
|
|
||||||
|
|
||||||
return restDamage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2367,7 +2326,6 @@ public class ComputerUtilCombat {
|
|||||||
* @return a int.
|
* @return a int.
|
||||||
*/
|
*/
|
||||||
public final static int predictDamageTo(final Card target, final int damage, final int possiblePrevention, final Card source, final boolean isCombat) {
|
public final static int predictDamageTo(final Card target, final int damage, final int possiblePrevention, final Card source, final boolean isCombat) {
|
||||||
|
|
||||||
int restDamage = damage;
|
int restDamage = damage;
|
||||||
|
|
||||||
restDamage = target.staticReplaceDamage(restDamage, source, isCombat);
|
restDamage = target.staticReplaceDamage(restDamage, source, isCombat);
|
||||||
@@ -2377,7 +2335,6 @@ public class ComputerUtilCombat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final static boolean dealsFirstStrikeDamage(final Card combatant, final boolean withoutAbilities, final Combat combat) {
|
public final static boolean dealsFirstStrikeDamage(final Card combatant, final boolean withoutAbilities, final Combat combat) {
|
||||||
|
|
||||||
if (combatant.hasKeyword(Keyword.DOUBLE_STRIKE) || combatant.hasKeyword(Keyword.FIRST_STRIKE)) {
|
if (combatant.hasKeyword(Keyword.DOUBLE_STRIKE) || combatant.hasKeyword(Keyword.FIRST_STRIKE)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2485,7 +2442,14 @@ public class ComputerUtilCombat {
|
|||||||
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(
|
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(
|
||||||
ReplacementType.DamageDone, repParams, ReplacementLayer.Other);
|
ReplacementType.DamageDone, repParams, ReplacementLayer.Other);
|
||||||
|
|
||||||
return !list.isEmpty();
|
for (final ReplacementEffect re : list) {
|
||||||
|
Map<String, String> params = re.getMapParams();
|
||||||
|
if (params.containsKey("Prevent") ||
|
||||||
|
(re.getOverridingAbility() != null && re.getOverridingAbility().getApi() != ApiType.ReplaceDamage && re.getOverridingAbility().getApi() != ApiType.ReplaceEffect)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean attackerHasThreateningAfflict(Card attacker, Player aiDefender) {
|
public static boolean attackerHasThreateningAfflict(Card attacker, Player aiDefender) {
|
||||||
@@ -2494,20 +2458,6 @@ public class ComputerUtilCombat {
|
|||||||
return afflictDmg > attacker.getNetPower() || afflictDmg >= aiDefender.getLife();
|
return afflictDmg > attacker.getNetPower() || afflictDmg >= aiDefender.getLife();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getMaxAttackersFor(final GameEntity defender) {
|
|
||||||
if (defender instanceof Player) {
|
|
||||||
for (final Card card : ((Player) defender).getCardsIn(ZoneType.Battlefield)) {
|
|
||||||
if (card.hasKeyword("No more than one creature can attack you each combat.")) {
|
|
||||||
return 1;
|
|
||||||
} else if (card.hasKeyword("No more than two creatures can attack you each combat.")) {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Card> categorizeAttackersByEvasion(List<Card> attackers) {
|
public static List<Card> categorizeAttackersByEvasion(List<Card> attackers) {
|
||||||
List<Card> categorizedAttackers = Lists.newArrayList();
|
List<Card> categorizedAttackers = Lists.newArrayList();
|
||||||
|
|
||||||
@@ -2597,5 +2547,3 @@ public class ComputerUtilCombat {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ package forge.ai;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.base.Predicates;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
@@ -19,6 +22,7 @@ import forge.game.card.CardCollection;
|
|||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardFactoryUtil;
|
import forge.game.card.CardFactoryUtil;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
@@ -137,9 +141,8 @@ public class ComputerUtilCost {
|
|||||||
* the source
|
* the source
|
||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public static boolean checkDiscardCost(final Player ai, final Cost cost, final Card source, SpellAbility sa) {
|
public static boolean checkDiscardCost(final Player ai, final Cost cost, final Card source, SpellAbility sa) {
|
||||||
if (cost == null || source.hasSVar("AISkipDiscardCostCheck") /* FIXME: should not be needed! */ ) {
|
if (cost == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +160,7 @@ public class ComputerUtilCost {
|
|||||||
if (typeList.size() > ai.getMaxHandSize()) {
|
if (typeList.size() > ai.getMaxHandSize()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int num = AbilityUtils.calculateAmount(source, disc.getAmount(), null);
|
int num = AbilityUtils.calculateAmount(source, disc.getAmount(), sa);
|
||||||
|
|
||||||
for (int i = 0; i < num; i++) {
|
for (int i = 0; i < num; i++) {
|
||||||
Card pref = ComputerUtil.getCardPreference(ai, source, "DiscardCost", typeList);
|
Card pref = ComputerUtil.getCardPreference(ai, source, "DiscardCost", typeList);
|
||||||
@@ -270,7 +273,10 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final CardCollection sacList = new CardCollection();
|
final CardCollection sacList = new CardCollection();
|
||||||
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility);
|
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility);
|
||||||
|
|
||||||
|
// don't sacrifice the card we're pumping
|
||||||
|
typeList = paymentChoicesWithoutTargets(typeList, sourceAbility, ai);
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
while (count < amount) {
|
while (count < amount) {
|
||||||
@@ -320,11 +326,14 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final CardCollection sacList = new CardCollection();
|
final CardCollection sacList = new CardCollection();
|
||||||
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility);
|
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility);
|
||||||
|
|
||||||
|
// don't sacrifice the card we're pumping
|
||||||
|
typeList = paymentChoicesWithoutTargets(typeList, sourceAbility, ai);
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
while (count < amount) {
|
while (count < amount) {
|
||||||
Card prefCard = ComputerUtil.getCardPreference(ai, source, "SacCost", typeList);
|
Card prefCard = ComputerUtil.getCardPreference(ai, source, "SacCost", typeList, sourceAbility);
|
||||||
if (prefCard == null) {
|
if (prefCard == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -337,6 +346,19 @@ public class ComputerUtilCost {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check sacrifice cost.
|
||||||
|
*
|
||||||
|
* @param cost
|
||||||
|
* the cost
|
||||||
|
* @param source
|
||||||
|
* the source
|
||||||
|
* @return true, if successful
|
||||||
|
*/
|
||||||
|
public static boolean checkSacrificeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sourceAbility) {
|
||||||
|
return checkSacrificeCost(ai, cost, source, sourceAbility, true);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isSacrificeSelfCost(final Cost cost) {
|
public static boolean isSacrificeSelfCost(final Cost cost) {
|
||||||
if (cost == null) {
|
if (cost == null) {
|
||||||
return false;
|
return false;
|
||||||
@@ -397,19 +419,6 @@ public class ComputerUtilCost {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check sacrifice cost.
|
|
||||||
*
|
|
||||||
* @param cost
|
|
||||||
* the cost
|
|
||||||
* @param source
|
|
||||||
* the source
|
|
||||||
* @return true, if successful
|
|
||||||
*/
|
|
||||||
public static boolean checkSacrificeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sourceAbility) {
|
|
||||||
return checkSacrificeCost(ai, cost, source, sourceAbility,true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* shouldPayCost.
|
* shouldPayCost.
|
||||||
@@ -420,8 +429,8 @@ public class ComputerUtilCost {
|
|||||||
* @param cost
|
* @param cost
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static boolean shouldPayCost(final Player ai, final Card hostCard, final Cost cost) {
|
public static boolean shouldPayCost(final Player ai, final Card hostCard, final Cost cost) {
|
||||||
|
|
||||||
for (final CostPart part : cost.getCostParts()) {
|
for (final CostPart part : cost.getCostParts()) {
|
||||||
if (part instanceof CostPayLife) {
|
if (part instanceof CostPayLife) {
|
||||||
if (!ai.cantLoseForZeroOrLessLife()) {
|
if (!ai.cantLoseForZeroOrLessLife()) {
|
||||||
@@ -558,7 +567,6 @@ public class ComputerUtilCost {
|
|||||||
boolean payForOwnOnly = "OnlyOwn".equals(aiLogic);
|
boolean payForOwnOnly = "OnlyOwn".equals(aiLogic);
|
||||||
boolean payOwner = sa.hasParam("UnlessAI") && aiLogic.startsWith("Defined");
|
boolean payOwner = sa.hasParam("UnlessAI") && aiLogic.startsWith("Defined");
|
||||||
boolean payNever = "Never".equals(aiLogic);
|
boolean payNever = "Never".equals(aiLogic);
|
||||||
boolean shockland = "Shockland".equals(aiLogic);
|
|
||||||
boolean isMine = sa.getActivatingPlayer().equals(payer);
|
boolean isMine = sa.getActivatingPlayer().equals(payer);
|
||||||
|
|
||||||
if (payNever) { return false; }
|
if (payNever) { return false; }
|
||||||
@@ -573,26 +581,6 @@ public class ComputerUtilCost {
|
|||||||
if (sa.getHostCard() == null || payer.equals(sa.getHostCard().getController())) {
|
if (sa.getHostCard() == null || payer.equals(sa.getHostCard().getController())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (shockland) {
|
|
||||||
if (payer.getLife() > 3 && payer.canPayLife(2)) {
|
|
||||||
final int landsize = payer.getLandsInPlay().size() + 1;
|
|
||||||
for (Card c : payer.getCardsIn(ZoneType.Hand)) {
|
|
||||||
// if the new land size would equal the CMC of a card in AIs hand, consider playing it untapped,
|
|
||||||
// otherwise don't bother running other checks
|
|
||||||
if (landsize != c.getCMC()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// try to determine in the AI is actually planning to play a spell ability from the card
|
|
||||||
boolean willPlay = ComputerUtil.hasReasonToPlayCardThisTurn(payer, c);
|
|
||||||
// try to determine if the mana shards provided by the lands would be applicable to pay the mana cost
|
|
||||||
boolean canPay = c.getManaCost().canBePaidWithAvaliable(ColorSet.fromNames(getAvailableManaColors(payer, source)).getColor());
|
|
||||||
|
|
||||||
if (canPay && willPlay) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else if ("Paralyze".equals(aiLogic)) {
|
} else if ("Paralyze".equals(aiLogic)) {
|
||||||
final Card c = source.getEnchantingCard();
|
final Card c = source.getEnchantingCard();
|
||||||
if (c == null || c.isUntapped()) {
|
if (c == null || c.isUntapped()) {
|
||||||
@@ -613,7 +601,7 @@ public class ComputerUtilCost {
|
|||||||
// if payer can't lose life its no need to pay unless
|
// if payer can't lose life its no need to pay unless
|
||||||
if (!payer.canLoseLife())
|
if (!payer.canLoseLife())
|
||||||
return false;
|
return false;
|
||||||
else if (payer.getLife() <= Integer.valueOf(aiLogic.substring(6))) {
|
else if (payer.getLife() <= AbilityUtils.calculateAmount(source, aiLogic.substring(6), sa)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if ("WillAttack".equals(aiLogic)) {
|
} else if ("WillAttack".equals(aiLogic)) {
|
||||||
@@ -630,6 +618,29 @@ public class ComputerUtilCost {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for shocklands and similar ETB replacement effects
|
||||||
|
if (sa.hasParam("ETB") && sa.getApi().equals(ApiType.Tap)) {
|
||||||
|
for (final CostPart part : cost.getCostParts()) {
|
||||||
|
if (part instanceof CostPayLife) {
|
||||||
|
final CostPayLife lifeCost = (CostPayLife) part;
|
||||||
|
Integer amount = lifeCost.convertAmount();
|
||||||
|
if (payer.getLife() > (amount + 1) && payer.canPayLife(amount)) {
|
||||||
|
final int landsize = payer.getLandsInPlay().size() + 1;
|
||||||
|
for (Card c : payer.getCardsIn(ZoneType.Hand)) {
|
||||||
|
// Check if the AI has enough lands to play the card
|
||||||
|
if (landsize != c.getCMC()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Check if the AI intends to play the card and if it can pay for it with the mana it has
|
||||||
|
boolean willPlay = ComputerUtil.hasReasonToPlayCardThisTurn(payer, c);
|
||||||
|
boolean canPay = c.getManaCost().canBePaidWithAvailable(ColorSet.fromNames(getAvailableManaColors(payer, source)).getColor());
|
||||||
|
return canPay && willPlay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AI will only pay when it's not already payed and only opponents abilities
|
// AI will only pay when it's not already payed and only opponents abilities
|
||||||
if (alreadyPaid || (payers.size() > 1 && (isMine && !payForOwnOnly))) {
|
if (alreadyPaid || (payers.size() > 1 && (isMine && !payForOwnOnly))) {
|
||||||
return false;
|
return false;
|
||||||
@@ -684,15 +695,16 @@ public class ComputerUtilCost {
|
|||||||
|
|
||||||
public static int getMaxXValue(SpellAbility sa, Player ai) {
|
public static int getMaxXValue(SpellAbility sa, Player ai) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final SpellAbility root = sa.getRootAbility();
|
SpellAbility root = sa.getRootAbility();
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = root.getPayCosts();
|
||||||
|
|
||||||
if (abCost == null || !abCost.hasXInAnyCostPart()) {
|
if (abCost == null || !abCost.hasXInAnyCostPart()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Integer val = null;
|
Integer val = null;
|
||||||
|
|
||||||
if (sa.costHasManaX()) {
|
if (root.costHasManaX()) {
|
||||||
val = ComputerUtilMana.determineLeftoverMana(root, ai);
|
val = ComputerUtilMana.determineLeftoverMana(root, ai);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -739,4 +751,12 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
return ObjectUtils.defaultIfNull(val, 0);
|
return ObjectUtils.defaultIfNull(val, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static CardCollection paymentChoicesWithoutTargets(Iterable<Card> choices, SpellAbility source, Player ai) {
|
||||||
|
if (source.usesTargeting()) {
|
||||||
|
final CardCollection targets = new CardCollection(source.getTargets().getTargetCards());
|
||||||
|
choices = Iterables.filter(choices, Predicates.not(Predicates.and(CardPredicates.isController(ai), Predicates.in(targets))));
|
||||||
|
}
|
||||||
|
return new CardCollection(choices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,8 @@
|
|||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.ArrayListMultimap;
|
import com.google.common.collect.*;
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import com.google.common.collect.ListMultimap;
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
import com.google.common.collect.Multimap;
|
|
||||||
|
|
||||||
import forge.ai.ability.AnimateAi;
|
import forge.ai.ability.AnimateAi;
|
||||||
import forge.card.ColorSet;
|
import forge.card.ColorSet;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
@@ -34,20 +16,10 @@ import forge.game.GameActionUtil;
|
|||||||
import forge.game.ability.AbilityKey;
|
import forge.game.ability.AbilityKey;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.*;
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardCollectionView;
|
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.card.CardPredicates;
|
|
||||||
import forge.game.card.CardUtil;
|
|
||||||
import forge.game.card.CounterEnumType;
|
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.*;
|
||||||
import forge.game.cost.CostAdjustment;
|
|
||||||
import forge.game.cost.CostPartMana;
|
|
||||||
import forge.game.cost.CostPayEnergy;
|
|
||||||
import forge.game.cost.CostPayment;
|
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.mana.Mana;
|
import forge.game.mana.Mana;
|
||||||
import forge.game.mana.ManaCostBeingPaid;
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
@@ -67,6 +39,10 @@ import forge.game.trigger.TriggerType;
|
|||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
public class ComputerUtilMana {
|
public class ComputerUtilMana {
|
||||||
private final static boolean DEBUG_MANA_PAYMENT = false;
|
private final static boolean DEBUG_MANA_PAYMENT = false;
|
||||||
@@ -272,28 +248,87 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
public static SpellAbility chooseManaAbility(ManaCostBeingPaid cost, SpellAbility sa, Player ai, ManaCostShard toPay,
|
public static SpellAbility chooseManaAbility(ManaCostBeingPaid cost, SpellAbility sa, Player ai, ManaCostShard toPay,
|
||||||
Collection<SpellAbility> saList, boolean checkCosts) {
|
Collection<SpellAbility> saList, boolean checkCosts) {
|
||||||
|
Card saHost = sa.getHostCard();
|
||||||
|
|
||||||
|
// CastTotalManaSpent (AIPreference:ManaFrom$Type or AIManaPref$ Type)
|
||||||
|
String manaSourceType = "";
|
||||||
|
if (saHost.hasSVar("AIPreference")) {
|
||||||
|
String condition = saHost.getSVar("AIPreference");
|
||||||
|
if (condition.startsWith("ManaFrom")) {
|
||||||
|
manaSourceType = TextUtil.split(condition, '$')[1];
|
||||||
|
}
|
||||||
|
} else if (sa.hasParam("AIManaPref")) {
|
||||||
|
manaSourceType = sa.getParam("AIManaPref");
|
||||||
|
}
|
||||||
|
if (manaSourceType != "") {
|
||||||
|
List<SpellAbility> filteredList = Lists.newArrayList(saList);
|
||||||
|
switch (manaSourceType) {
|
||||||
|
case "Snow":
|
||||||
|
filteredList.sort(new Comparator<SpellAbility>() {
|
||||||
|
@Override
|
||||||
|
public int compare(SpellAbility ab1, SpellAbility ab2) {
|
||||||
|
return ab1.getHostCard() != null && ab1.getHostCard().isSnow()
|
||||||
|
&& ab2.getHostCard() != null && !ab2.getHostCard().isSnow() ? -1 : 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
saList = filteredList;
|
||||||
|
break;
|
||||||
|
case "Treasure":
|
||||||
|
// Try to spend only one Treasure if possible
|
||||||
|
filteredList.sort(new Comparator<SpellAbility>() {
|
||||||
|
@Override
|
||||||
|
public int compare(SpellAbility ab1, SpellAbility ab2) {
|
||||||
|
return ab1.getHostCard() != null && ab1.getHostCard().getType().hasSubtype("Treasure")
|
||||||
|
&& ab2.getHostCard() != null && !ab2.getHostCard().getType().hasSubtype("Treasure") ? -1 : 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
SpellAbility first = filteredList.get(0);
|
||||||
|
if (first.getHostCard() != null && first.getHostCard().getType().hasSubtype("Treasure")) {
|
||||||
|
saList.remove(first);
|
||||||
|
List<SpellAbility> updatedList = Lists.newArrayList();
|
||||||
|
updatedList.add(first);
|
||||||
|
updatedList.addAll(saList);
|
||||||
|
saList = updatedList;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "TreasureMax":
|
||||||
|
// Ok to spend as many Treasures as possible
|
||||||
|
filteredList.sort(new Comparator<SpellAbility>() {
|
||||||
|
@Override
|
||||||
|
public int compare(SpellAbility ab1, SpellAbility ab2) {
|
||||||
|
return ab1.getHostCard() != null && ab1.getHostCard().getType().hasSubtype("Treasure")
|
||||||
|
&& ab2.getHostCard() != null && !ab2.getHostCard().getType().hasSubtype("Treasure") ? -1 : 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
saList = filteredList;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (final SpellAbility ma : saList) {
|
for (final SpellAbility ma : saList) {
|
||||||
if (ma.getHostCard() == sa.getHostCard()) {
|
if (ma.getHostCard() == saHost) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getHostCard() != null) {
|
if (saHost != null) {
|
||||||
if (sa.getApi() == ApiType.Animate) {
|
if (sa.getApi() == ApiType.Animate) {
|
||||||
// For abilities like Genju of the Cedars, make sure that we're not activating the aura ability by tapping the enchanted card for mana
|
// For abilities like Genju of the Cedars, make sure that we're not activating the aura ability by tapping the enchanted card for mana
|
||||||
if (sa.getHostCard().isAura() && "Enchanted".equals(sa.getParam("Defined"))
|
if (saHost.isAura() && "Enchanted".equals(sa.getParam("Defined"))
|
||||||
&& ma.getHostCard() == sa.getHostCard().getEnchantingCard()
|
&& ma.getHostCard() == saHost.getEnchantingCard()
|
||||||
&& ma.getPayCosts().hasTapCost()) {
|
&& ma.getPayCosts().hasTapCost()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a manland was previously animated this turn, do not tap it to animate another manland
|
// If a manland was previously animated this turn, do not tap it to animate another manland
|
||||||
if (sa.getHostCard().isLand() && ma.getHostCard().isLand()
|
if (saHost.isLand() && ma.getHostCard().isLand()
|
||||||
&& ai.getController().isAI()
|
&& ai.getController().isAI()
|
||||||
&& AnimateAi.isAnimatedThisTurn(ai, ma.getHostCard())) {
|
&& AnimateAi.isAnimatedThisTurn(ai, ma.getHostCard())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else if (sa.getApi() == ApiType.Pump) {
|
} else if (sa.getApi() == ApiType.Pump) {
|
||||||
if ((sa.getHostCard().isInstant() || sa.getHostCard().isSorcery())
|
if ((saHost.isInstant() || saHost.isSorcery())
|
||||||
&& ma.getHostCard().isCreature()
|
&& ma.getHostCard().isCreature()
|
||||||
&& ai.getController().isAI()
|
&& ai.getController().isAI()
|
||||||
&& ma.getPayCosts().hasTapCost()
|
&& ma.getPayCosts().hasTapCost()
|
||||||
@@ -303,7 +338,7 @@ public class ComputerUtilMana {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else if (sa.getApi() == ApiType.Attach
|
} else if (sa.getApi() == ApiType.Attach
|
||||||
&& "AvoidPayingWithAttachTarget".equals(sa.getHostCard().getSVar("AIPaymentPreference"))) {
|
&& "AvoidPayingWithAttachTarget".equals(saHost.getSVar("AIPaymentPreference"))) {
|
||||||
// For cards like Genju of the Cedars, make sure we're not attaching to the same land that will
|
// For cards like Genju of the Cedars, make sure we're not attaching to the same land that will
|
||||||
// be tapped to pay its own cost if there's another untapped land like that available
|
// be tapped to pay its own cost if there's another untapped land like that available
|
||||||
if (ma.getHostCard().equals(sa.getTargetCard())) {
|
if (ma.getHostCard().equals(sa.getTargetCard())) {
|
||||||
@@ -319,8 +354,8 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
// Exception: when paying generic mana with Cavern of Souls, prefer the colored mana producing ability
|
// Exception: when paying generic mana with Cavern of Souls, prefer the colored mana producing ability
|
||||||
// to attempt to make the spell uncounterable when possible.
|
// to attempt to make the spell uncounterable when possible.
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(ma).equals("Cavern of Souls") && ma.hasChosenType()
|
if (ComputerUtilAbility.getAbilitySourceName(ma).equals("Cavern of Souls")
|
||||||
&& sa.getHostCard().getType().getCreatureTypes().contains(ma.getChosenType(0))) {
|
&& saHost.getType().getCreatureTypes().contains(ma.getHostCard().getChosenType())) {
|
||||||
if (toPay == ManaCostShard.COLORLESS && cost.getUnpaidShards().contains(ManaCostShard.GENERIC)) {
|
if (toPay == ManaCostShard.COLORLESS && cost.getUnpaidShards().contains(ManaCostShard.GENERIC)) {
|
||||||
// Deprioritize Cavern of Souls, try to pay generic mana with it instead to use the NoCounter ability
|
// Deprioritize Cavern of Souls, try to pay generic mana with it instead to use the NoCounter ability
|
||||||
continue;
|
continue;
|
||||||
@@ -341,7 +376,7 @@ public class ComputerUtilMana {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts)) {
|
if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts, cost.getXManaCostPaidByColor())) {
|
||||||
return paymentChoice;
|
return paymentChoice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -414,6 +449,7 @@ public class ComputerUtilMana {
|
|||||||
// then apply this one
|
// then apply this one
|
||||||
if (!replaceType.isEmpty()) {
|
if (!replaceType.isEmpty()) {
|
||||||
for (SpellAbility saMana : replaceAmount) {
|
for (SpellAbility saMana : replaceAmount) {
|
||||||
|
Card card = saMana.getHostCard();
|
||||||
if (saMana.hasParam("ReplaceType")) {
|
if (saMana.hasParam("ReplaceType")) {
|
||||||
// replace color and colorless
|
// replace color and colorless
|
||||||
String color = saMana.getParam("ReplaceType");
|
String color = saMana.getParam("ReplaceType");
|
||||||
@@ -435,8 +471,8 @@ public class ComputerUtilMana {
|
|||||||
// replace color
|
// replace color
|
||||||
String color = saMana.getParam("ReplaceColor");
|
String color = saMana.getParam("ReplaceColor");
|
||||||
if ("Chosen".equals(color)) {
|
if ("Chosen".equals(color)) {
|
||||||
if (saMana.hasChosenColor()) {
|
if (card.hasChosenColor()) {
|
||||||
color = MagicColor.toShortString(saMana.getChosenColor());
|
color = MagicColor.toShortString(card.getChosenColor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (saMana.hasParam("ReplaceOnly")) {
|
if (saMana.hasParam("ReplaceOnly")) {
|
||||||
@@ -488,7 +524,7 @@ public class ComputerUtilMana {
|
|||||||
int pAmount = AbilityUtils.calculateAmount(trSA.getHostCard(), trSA.getParamOrDefault("Amount", "1"), trSA);
|
int pAmount = AbilityUtils.calculateAmount(trSA.getHostCard(), trSA.getParamOrDefault("Amount", "1"), trSA);
|
||||||
String produced = trSA.getParam("Produced");
|
String produced = trSA.getParam("Produced");
|
||||||
if (produced.equals("Chosen")) {
|
if (produced.equals("Chosen")) {
|
||||||
produced = MagicColor.toShortString(trSA.getHostCard().getChosenColor(trSA));
|
produced = MagicColor.toShortString(trSA.getHostCard().getChosenColor());
|
||||||
}
|
}
|
||||||
manaProduced += " " + StringUtils.repeat(produced, pAmount);
|
manaProduced += " " + StringUtils.repeat(produced, pAmount);
|
||||||
} else if (ApiType.ManaReflected.equals(trSA.getApi())) {
|
} else if (ApiType.ManaReflected.equals(trSA.getApi())) {
|
||||||
@@ -546,7 +582,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get a mana of this type from floating, bail if none available
|
// get a mana of this type from floating, bail if none available
|
||||||
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), (byte) -1);
|
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), (byte) -1, cost.getXManaCostPaidByColor());
|
||||||
if (mana != null) {
|
if (mana != null) {
|
||||||
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, false)) {
|
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, false)) {
|
||||||
manaSpentToPay.add(0, mana);
|
manaSpentToPay.add(0, mana);
|
||||||
@@ -608,7 +644,6 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
|
String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
|
||||||
|
|
||||||
//System.out.println(manaProduced);
|
|
||||||
payMultipleMana(cost, manaProduced, ai);
|
payMultipleMana(cost, manaProduced, ai);
|
||||||
|
|
||||||
// remove from available lists
|
// remove from available lists
|
||||||
@@ -788,13 +823,11 @@ public class ComputerUtilMana {
|
|||||||
String manaProduced = ignoreColor || ignoreType ? MagicColor.toShortString(toPay.getColorMask())
|
String manaProduced = ignoreColor || ignoreType ? MagicColor.toShortString(toPay.getColorMask())
|
||||||
: predictManafromSpellAbility(saPayment, ai, toPay);
|
: predictManafromSpellAbility(saPayment, ai, toPay);
|
||||||
|
|
||||||
// System.out.println(manaProduced);
|
|
||||||
payMultipleMana(cost, manaProduced, ai);
|
payMultipleMana(cost, manaProduced, ai);
|
||||||
|
|
||||||
// remove from available lists
|
// remove from available lists
|
||||||
Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard()));
|
Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard()));
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
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);
|
||||||
@@ -829,8 +862,7 @@ public class ComputerUtilMana {
|
|||||||
if (test) {
|
if (test) {
|
||||||
resetPayment(paymentList);
|
resetPayment(paymentList);
|
||||||
return false;
|
return false;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
System.out.println("ComputerUtil : payManaCost() cost was not paid for " + sa.getHostCard().getName() + ". Didn't find what to pay for " + toPay);
|
System.out.println("ComputerUtil : payManaCost() cost was not paid for " + sa.getHostCard().getName() + ". Didn't find what to pay for " + toPay);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -935,7 +967,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get a mana of this type from floating, bail if none available
|
// get a mana of this type from floating, bail if none available
|
||||||
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), hasConverge ? cost.getColorsPaid() : -1);
|
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), hasConverge ? cost.getColorsPaid() : -1, cost.getXManaCostPaidByColor());
|
||||||
if (mana != null) {
|
if (mana != null) {
|
||||||
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, test)) {
|
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, test)) {
|
||||||
manaSpentToPay.add(0, mana);
|
manaSpentToPay.add(0, mana);
|
||||||
@@ -964,8 +996,10 @@ public class ComputerUtilMana {
|
|||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a {@link forge.game.mana.Mana} object.
|
* @return a {@link forge.game.mana.Mana} object.
|
||||||
*/
|
*/
|
||||||
private static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid) {
|
private static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor,
|
||||||
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard, saBeingPaidFor, restriction, colorsPaid);
|
String restriction, final byte colorsPaid, Map<String, Integer> xManaCostPaidByColor) {
|
||||||
|
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard,
|
||||||
|
saBeingPaidFor, restriction, colorsPaid, xManaCostPaidByColor);
|
||||||
|
|
||||||
// Exclude border case
|
// Exclude border case
|
||||||
if (weightedOptions.isEmpty()) {
|
if (weightedOptions.isEmpty()) {
|
||||||
@@ -1014,9 +1048,13 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static List<Pair<Mana, Integer>> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard,
|
private static List<Pair<Mana, Integer>> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard,
|
||||||
final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid) {
|
final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid, Map<String, Integer> xManaCostPaidByColor) {
|
||||||
final List<Pair<Mana, Integer>> weightedOptions = new ArrayList<>();
|
final List<Pair<Mana, Integer>> weightedOptions = new ArrayList<>();
|
||||||
for (final Mana thisMana : manapool) {
|
for (final Mana thisMana : manapool) {
|
||||||
|
if (shard == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(MagicColor.toShortString(thisMana.getColor()), xManaCostPaidByColor)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
|
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1066,7 +1104,6 @@ public class ComputerUtilMana {
|
|||||||
getComboManaChoice(ai, saPayment, sa, cost);
|
getComboManaChoice(ai, saPayment, sa, cost);
|
||||||
}
|
}
|
||||||
else if (saPayment.getApi() == ApiType.ManaReflected) {
|
else if (saPayment.getApi() == ApiType.ManaReflected) {
|
||||||
//System.out.println("Evaluate reflected mana of: " + saPayment.getHostCard());
|
|
||||||
Set<String> reflected = CardUtil.getReflectableManaColors(saPayment);
|
Set<String> reflected = CardUtil.getReflectableManaColors(saPayment);
|
||||||
|
|
||||||
for (byte c : MagicColor.WUBRG) {
|
for (byte c : MagicColor.WUBRG) {
|
||||||
@@ -1092,7 +1129,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts) {
|
private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts, Map<String, Integer> xManaCostPaidByColor) {
|
||||||
final Card sourceCard = ma.getHostCard();
|
final Card sourceCard = ma.getHostCard();
|
||||||
|
|
||||||
if (isManaSourceReserved(ai, sourceCard, sa)) {
|
if (isManaSourceReserved(ai, sourceCard, sa)) {
|
||||||
@@ -1130,6 +1167,10 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
if (m.isComboMana()) {
|
if (m.isComboMana()) {
|
||||||
for (String s : m.getComboColors().split(" ")) {
|
for (String s : m.getComboColors().split(" ")) {
|
||||||
|
if (toPay == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(s, xManaCostPaidByColor)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ("Any".equals(s) || ai.getManaPool().canPayForShardWithColor(toPay, ManaAtom.fromName(s)))
|
if ("Any".equals(s) || ai.getManaPool().canPayForShardWithColor(toPay, ManaAtom.fromName(s)))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1140,6 +1181,9 @@ public class ComputerUtilMana {
|
|||||||
Set<String> reflected = CardUtil.getReflectableManaColors(ma);
|
Set<String> reflected = CardUtil.getReflectableManaColors(ma);
|
||||||
|
|
||||||
for (byte c : MagicColor.WUBRG) {
|
for (byte c : MagicColor.WUBRG) {
|
||||||
|
if (toPay == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(MagicColor.toShortString(c), xManaCostPaidByColor)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (ai.getManaPool().canPayForShardWithColor(toPay, c) && reflected.contains(MagicColor.toLongString(c))) {
|
if (ai.getManaPool().canPayForShardWithColor(toPay, c) && reflected.contains(MagicColor.toLongString(c))) {
|
||||||
m.setExpressChoice(MagicColor.toShortString(c));
|
m.setExpressChoice(MagicColor.toShortString(c));
|
||||||
return true;
|
return true;
|
||||||
@@ -1147,6 +1191,16 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (toPay == ManaCostShard.COLORED_X) {
|
||||||
|
for (String s : m.mana().split(" ")) {
|
||||||
|
if (ManaCostBeingPaid.canColoredXShardBePaidByColor(s, xManaCostPaidByColor)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1177,8 +1231,7 @@ public class ComputerUtilMana {
|
|||||||
if (!(ai.getGame().getPhaseHandler().isPlayerTurn(ai))) {
|
if (!(ai.getGame().getPhaseHandler().isPlayerTurn(ai))) {
|
||||||
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
|
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_ENEMY_DECLBLK);
|
||||||
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT);
|
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.CHOSEN_FOG_EFFECT);
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK);
|
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK);
|
||||||
} else {
|
} else {
|
||||||
if ((AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK)) ||
|
if ((AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_DECLBLK)) ||
|
||||||
@@ -1199,8 +1252,7 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
if (curPhase == PhaseType.MAIN2 || curPhase == PhaseType.CLEANUP) {
|
if (curPhase == PhaseType.MAIN2 || curPhase == PhaseType.CLEANUP) {
|
||||||
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
|
AiCardMemory.clearMemorySet(ai, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
if (AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2)) {
|
if (AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2)) {
|
||||||
// This mana source is held elsewhere for a Main Phase 2 spell.
|
// This mana source is held elsewhere for a Main Phase 2 spell.
|
||||||
return true;
|
return true;
|
||||||
@@ -1210,7 +1262,6 @@ public class ComputerUtilMana {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static ManaCostShard getNextShardToPay(ManaCostBeingPaid cost) {
|
private static ManaCostShard getNextShardToPay(ManaCostBeingPaid cost) {
|
||||||
// mind the priorities
|
// mind the priorities
|
||||||
// * Pay mono-colored first,curPhase == PhaseType.CLEANUP
|
// * Pay mono-colored first,curPhase == PhaseType.CLEANUP
|
||||||
@@ -1258,7 +1309,7 @@ public class ComputerUtilMana {
|
|||||||
final AbilityManaPart abMana = manaAb.getManaPart();
|
final AbilityManaPart abMana = manaAb.getManaPart();
|
||||||
|
|
||||||
if (abMana.isComboMana()) {
|
if (abMana.isComboMana()) {
|
||||||
int amount = manaAb.hasParam("Amount") ? AbilityUtils.calculateAmount(source, manaAb.getParam("Amount"), saRoot) : 1;
|
int amount = manaAb.hasParam("Amount") ? AbilityUtils.calculateAmount(source, manaAb.getParam("Amount"), manaAb) : 1;
|
||||||
final ManaCostBeingPaid testCost = new ManaCostBeingPaid(cost);
|
final ManaCostBeingPaid testCost = new ManaCostBeingPaid(cost);
|
||||||
final String[] comboColors = abMana.getComboColors().split(" ");
|
final String[] comboColors = abMana.getComboColors().split(" ");
|
||||||
for (int nMana = 1; nMana <= amount; nMana++) {
|
for (int nMana = 1; nMana <= amount; nMana++) {
|
||||||
@@ -1278,7 +1329,7 @@ public class ComputerUtilMana {
|
|||||||
if (!testCost.isPaid()) {
|
if (!testCost.isPaid()) {
|
||||||
// Loop over combo colors
|
// Loop over combo colors
|
||||||
for (String color : comboColors) {
|
for (String color : comboColors) {
|
||||||
if (satisfiesColorChoice(abMana, choiceString, choice) && testCost.isAnyPartPayableWith(ManaAtom.fromName(color), ai.getManaPool())) {
|
if (satisfiesColorChoice(abMana, choiceString, choice) && testCost.needsColor(ManaAtom.fromName(color), ai.getManaPool())) {
|
||||||
payMultipleMana(testCost, color, ai);
|
payMultipleMana(testCost, color, ai);
|
||||||
if (nMana != 1) {
|
if (nMana != 1) {
|
||||||
choiceString.append(" ");
|
choiceString.append(" ");
|
||||||
@@ -1296,8 +1347,7 @@ public class ComputerUtilMana {
|
|||||||
String commonColor = ComputerUtilCard.getMostProminentColor(ai.getCardsIn(ZoneType.Hand));
|
String commonColor = ComputerUtilCard.getMostProminentColor(ai.getCardsIn(ZoneType.Hand));
|
||||||
if (!commonColor.isEmpty() && satisfiesColorChoice(abMana, choiceString, MagicColor.toShortString(commonColor)) && abMana.getComboColors().contains(MagicColor.toShortString(commonColor))) {
|
if (!commonColor.isEmpty() && satisfiesColorChoice(abMana, choiceString, MagicColor.toShortString(commonColor)) && abMana.getComboColors().contains(MagicColor.toShortString(commonColor))) {
|
||||||
choice = MagicColor.toShortString(commonColor);
|
choice = MagicColor.toShortString(commonColor);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// default to first available color
|
// default to first available color
|
||||||
for (String c : comboColors) {
|
for (String c : comboColors) {
|
||||||
if (satisfiesColorChoice(abMana, choiceString, c)) {
|
if (satisfiesColorChoice(abMana, choiceString, c)) {
|
||||||
@@ -1342,8 +1392,7 @@ public class ComputerUtilMana {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
String color = MagicColor.toShortString(manaPart);
|
String color = MagicColor.toShortString(manaPart);
|
||||||
boolean wasNeeded = testCost.ai_payMana(color, p.getManaPool());
|
boolean wasNeeded = testCost.ai_payMana(color, p.getManaPool());
|
||||||
if (!wasNeeded) {
|
if (!wasNeeded) {
|
||||||
@@ -1426,24 +1475,33 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
String restriction = null;
|
String restriction = null;
|
||||||
if (payCosts != null && payCosts.getCostMana() != null) {
|
if (payCosts != null && payCosts.getCostMana() != null) {
|
||||||
restriction = payCosts.getCostMana().getRestiction();
|
restriction = payCosts.getCostMana().getRestriction();
|
||||||
}
|
}
|
||||||
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 (cost.getXcounter() > 0 || extraMana > 0) {
|
if (cost.getXcounter() > 0 || extraMana > 0) {
|
||||||
int manaToAdd = 0;
|
int manaToAdd = 0;
|
||||||
|
int xCounter = cost.getXcounter();
|
||||||
if (test && extraMana > 0) {
|
if (test && extraMana > 0) {
|
||||||
final int multiplicator = Math.max(cost.getXcounter(), 1);
|
final int multiplicator = Math.max(xCounter, 1);
|
||||||
manaToAdd = extraMana * multiplicator;
|
manaToAdd = extraMana * multiplicator;
|
||||||
} else {
|
} else {
|
||||||
manaToAdd = AbilityUtils.calculateAmount(card, "X", sa) * cost.getXcounter();
|
manaToAdd = AbilityUtils.calculateAmount(card, "X", sa) * xCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
cost.increaseShard(ManaCostShard.parseNonGeneric(sa.getParamOrDefault("XColor", "1")), manaToAdd);
|
String xColor = sa.getParamOrDefault("XColor", "1");
|
||||||
|
if (card.hasKeyword("Spend only colored mana on X. No more than one mana of each color may be spent this way.")) {
|
||||||
|
xColor = "WUBRGX";
|
||||||
|
}
|
||||||
|
if (xCounter > 0) {
|
||||||
|
cost.setXManaCostPaid(manaToAdd / xCounter, xColor);
|
||||||
|
} else {
|
||||||
|
cost.increaseShard(ManaCostShard.parseNonGeneric(xColor), manaToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
if (!test) {
|
if (!test) {
|
||||||
sa.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
sa.setXManaCostPaid(manaToAdd / xCounter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1530,7 +1588,7 @@ public class ComputerUtilMana {
|
|||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
for (final SpellAbility am : getAIPlayableMana(c)) {
|
for (final SpellAbility am : getAIPlayableMana(c)) {
|
||||||
am.setActivatingPlayer(ai);
|
am.setActivatingPlayer(ai);
|
||||||
if (!checkPlayable || am.canPlay()) {
|
if (!checkPlayable || (am.canPlay() && am.checkRestrictions(ai))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1834,8 +1892,7 @@ public class ComputerUtilMana {
|
|||||||
if (!res.contains(a)) {
|
if (!res.contains(a)) {
|
||||||
if (cost.isReusuableResource()) {
|
if (cost.isReusuableResource()) {
|
||||||
res.add(0, a);
|
res.add(0, a);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
res.add(res.size(), a);
|
res.add(res.size(), a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1848,7 +1905,7 @@ public class ComputerUtilMana {
|
|||||||
final Card offering = sa.getSacrificedAsOffering();
|
final Card offering = sa.getSacrificedAsOffering();
|
||||||
offering.setUsedToPay(false);
|
offering.setUsedToPay(false);
|
||||||
if (costIsPaid && !test) {
|
if (costIsPaid && !test) {
|
||||||
sa.getHostCard().getGame().getAction().sacrifice(offering, sa, null);
|
sa.getHostCard().getGame().getAction().sacrifice(offering, sa, null, null);
|
||||||
}
|
}
|
||||||
sa.resetSacrificedAsOffering();
|
sa.resetSacrificedAsOffering();
|
||||||
}
|
}
|
||||||
@@ -1856,7 +1913,7 @@ public class ComputerUtilMana {
|
|||||||
final Card emerge = sa.getSacrificedAsEmerge();
|
final Card emerge = sa.getSacrificedAsEmerge();
|
||||||
emerge.setUsedToPay(false);
|
emerge.setUsedToPay(false);
|
||||||
if (costIsPaid && !test) {
|
if (costIsPaid && !test) {
|
||||||
sa.getHostCard().getGame().getAction().sacrifice(emerge, sa, null);
|
sa.getHostCard().getGame().getAction().sacrifice(emerge, sa, null, null);
|
||||||
}
|
}
|
||||||
sa.resetSacrificedAsEmerge();
|
sa.resetSacrificedAsEmerge();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,9 +162,9 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
|||||||
value -= subValue(10, "must-attack");
|
value -= subValue(10, "must-attack");
|
||||||
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
|
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
|
||||||
value -= subValue(10, "must-attack-player");
|
value -= subValue(10, "must-attack-player");
|
||||||
} else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
|
}/* else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
|
||||||
value -= subValue(toughness * 5, "reverse-reach");
|
value -= subValue(toughness * 5, "reverse-reach");
|
||||||
}
|
}//*/
|
||||||
|
|
||||||
if (c.hasSVar("DestroyWhenDamaged")) {
|
if (c.hasSVar("DestroyWhenDamaged")) {
|
||||||
value -= subValue((toughness - 1) * 9, "dies-to-dmg");
|
value -= subValue((toughness - 1) * 9, "dies-to-dmg");
|
||||||
|
|||||||
@@ -3,23 +3,12 @@ package forge.ai;
|
|||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.EnumMap;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
|
import com.google.common.collect.*;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import com.google.common.collect.ArrayListMultimap;
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
import com.google.common.collect.Multimap;
|
|
||||||
|
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
import forge.card.CardStateName;
|
import forge.card.CardStateName;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
@@ -27,7 +16,6 @@ import forge.game.Game;
|
|||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
import forge.game.ability.AbilityKey;
|
|
||||||
import forge.game.ability.effects.DetachedCardEffect;
|
import forge.game.ability.effects.DetachedCardEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCloneStates;
|
import forge.game.card.CardCloneStates;
|
||||||
@@ -44,9 +32,7 @@ import forge.game.mana.ManaPool;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilityManaPart;
|
import forge.game.spellability.AbilityManaPart;
|
||||||
import forge.game.spellability.AbilitySub;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.trigger.TriggerType;
|
|
||||||
import forge.game.zone.PlayerZone;
|
import forge.game.zone.PlayerZone;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.item.IPaperCard;
|
import forge.item.IPaperCard;
|
||||||
@@ -63,6 +49,7 @@ public abstract class GameState {
|
|||||||
ZONES.put(ZoneType.Library, "library");
|
ZONES.put(ZoneType.Library, "library");
|
||||||
ZONES.put(ZoneType.Exile, "exile");
|
ZONES.put(ZoneType.Exile, "exile");
|
||||||
ZONES.put(ZoneType.Command, "command");
|
ZONES.put(ZoneType.Command, "command");
|
||||||
|
ZONES.put(ZoneType.Sideboard, "sideboard");
|
||||||
}
|
}
|
||||||
|
|
||||||
private int humanLife = -1;
|
private int humanLife = -1;
|
||||||
@@ -89,7 +76,8 @@ public abstract class GameState {
|
|||||||
private final Map<Card, Integer> markedDamage = new HashMap<>();
|
private final Map<Card, Integer> markedDamage = new HashMap<>();
|
||||||
private final Map<Card, List<String>> cardToChosenClrs = new HashMap<>();
|
private final Map<Card, List<String>> cardToChosenClrs = new HashMap<>();
|
||||||
private final Map<Card, CardCollection> cardToChosenCards = new HashMap<>();
|
private final Map<Card, CardCollection> cardToChosenCards = new HashMap<>();
|
||||||
private final Map<Card, List<String>> cardToChosenTypes = new HashMap<>();
|
private final Map<Card, String> cardToChosenType = new HashMap<>();
|
||||||
|
private final Map<Card, String> cardToChosenType2 = new HashMap<>();
|
||||||
private final Map<Card, List<String>> cardToRememberedId = new HashMap<>();
|
private final Map<Card, List<String>> cardToRememberedId = new HashMap<>();
|
||||||
private final Map<Card, List<String>> cardToImprintedId = new HashMap<>();
|
private final Map<Card, List<String>> cardToImprintedId = new HashMap<>();
|
||||||
private final Map<Card, List<String>> cardToMergedCards = new HashMap<>();
|
private final Map<Card, List<String>> cardToMergedCards = new HashMap<>();
|
||||||
@@ -340,16 +328,15 @@ public abstract class GameState {
|
|||||||
newText.append("|Damage:").append(c.getDamage());
|
newText.append("|Damage:").append(c.getDamage());
|
||||||
}
|
}
|
||||||
|
|
||||||
SpellAbility first = c.getFirstSpellAbility();
|
if (!c.getChosenColor().isEmpty()) {
|
||||||
if (first != null) {
|
newText.append("|ChosenColor:").append(TextUtil.join(c.getChosenColors(), ","));
|
||||||
if (first.hasChosenColor()) {
|
|
||||||
newText.append("|ChosenColor:").append(TextUtil.join(first.getChosenColors(), ","));
|
|
||||||
}
|
}
|
||||||
if (first.hasChosenType()) {
|
if (!c.getChosenType().isEmpty()) {
|
||||||
newText.append("|ChosenType:").append(TextUtil.join(first.getChosenType(), ","));
|
newText.append("|ChosenType:").append(c.getChosenType());
|
||||||
}
|
}
|
||||||
|
if (!c.getChosenType2().isEmpty()) {
|
||||||
|
newText.append("|ChosenType2:").append(c.getChosenType2());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!c.getNamedCard().isEmpty()) {
|
if (!c.getNamedCard().isEmpty()) {
|
||||||
newText.append("|NamedCard:").append(c.getNamedCard());
|
newText.append("|NamedCard:").append(c.getNamedCard());
|
||||||
}
|
}
|
||||||
@@ -412,6 +399,12 @@ public abstract class GameState {
|
|||||||
// Need to figure out a better way to detect if it's actually on adventure.
|
// Need to figure out a better way to detect if it's actually on adventure.
|
||||||
newText.append("|OnAdventure");
|
newText.append("|OnAdventure");
|
||||||
}
|
}
|
||||||
|
if (c.isForetold()) {
|
||||||
|
newText.append("|Foretold");
|
||||||
|
}
|
||||||
|
if (c.isForetoldThisTurn()) {
|
||||||
|
newText.append("|ForetoldThisTurn");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -579,6 +572,13 @@ public abstract class GameState {
|
|||||||
aiCardTexts.put(ZoneType.Command, categoryValue);
|
aiCardTexts.put(ZoneType.Command, categoryValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (categoryName.endsWith("sideboard")) {
|
||||||
|
if (isHuman)
|
||||||
|
humanCardTexts.put(ZoneType.Sideboard, categoryValue);
|
||||||
|
else
|
||||||
|
aiCardTexts.put(ZoneType.Sideboard, categoryValue);
|
||||||
|
}
|
||||||
|
|
||||||
else if (categoryName.startsWith("ability")) {
|
else if (categoryName.startsWith("ability")) {
|
||||||
abilityString.put(categoryName.substring("ability".length()), categoryValue);
|
abilityString.put(categoryName.substring("ability".length()), categoryValue);
|
||||||
}
|
}
|
||||||
@@ -638,7 +638,8 @@ public abstract class GameState {
|
|||||||
markedDamage.clear();
|
markedDamage.clear();
|
||||||
cardToChosenClrs.clear();
|
cardToChosenClrs.clear();
|
||||||
cardToChosenCards.clear();
|
cardToChosenCards.clear();
|
||||||
cardToChosenTypes.clear();
|
cardToChosenType.clear();
|
||||||
|
cardToChosenType2.clear();
|
||||||
cardToMergedCards.clear();
|
cardToMergedCards.clear();
|
||||||
cardToScript.clear();
|
cardToScript.clear();
|
||||||
cardAttackMap.clear();
|
cardAttackMap.clear();
|
||||||
@@ -733,7 +734,7 @@ public abstract class GameState {
|
|||||||
if (persistent) {
|
if (persistent) {
|
||||||
produced.put("PersistentMana", "True");
|
produced.put("PersistentMana", "True");
|
||||||
}
|
}
|
||||||
final AbilityManaPart abMana = new AbilityManaPart(dummy, null, produced);
|
final AbilityManaPart abMana = new AbilityManaPart(dummy, produced);
|
||||||
game.getAction().invoke(new Runnable() {
|
game.getAction().invoke(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -769,23 +770,10 @@ public abstract class GameState {
|
|||||||
}
|
}
|
||||||
game.fireEvent(new GameEventAttackersDeclared(attackingPlayer, attackersMap));
|
game.fireEvent(new GameEventAttackersDeclared(attackingPlayer, attackersMap));
|
||||||
|
|
||||||
if (!combat.getAttackers().isEmpty()) {
|
|
||||||
List<GameEntity> attackedTarget = Lists.newArrayList();
|
|
||||||
for (final Card c : combat.getAttackers()) {
|
for (final Card c : combat.getAttackers()) {
|
||||||
attackedTarget.add(combat.getDefenderByAttacker(c));
|
CombatUtil.checkDeclaredAttacker(game, c, combat, false);
|
||||||
}
|
|
||||||
final Map<AbilityKey, Object> runParams = Maps.newEnumMap(AbilityKey.class);
|
|
||||||
runParams.put(AbilityKey.Attackers, combat.getAttackers());
|
|
||||||
runParams.put(AbilityKey.AttackingPlayer, combat.getAttackingPlayer());
|
|
||||||
runParams.put(AbilityKey.AttackedTarget, attackedTarget);
|
|
||||||
game.getTriggerHandler().runTrigger(TriggerType.AttackersDeclared, runParams, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final Card c : combat.getAttackers()) {
|
|
||||||
CombatUtil.checkDeclaredAttacker(game, c, combat);
|
|
||||||
}
|
|
||||||
|
|
||||||
game.getTriggerHandler().resetActiveTriggers();
|
|
||||||
game.updateCombatForView();
|
game.updateCombatForView();
|
||||||
game.fireEvent(new GameEventCombatChanged());
|
game.fireEvent(new GameEventCombatChanged());
|
||||||
|
|
||||||
@@ -828,7 +816,6 @@ public abstract class GameState {
|
|||||||
Card exiledWith = idToCard.get(Integer.parseInt(id));
|
Card exiledWith = idToCard.get(Integer.parseInt(id));
|
||||||
c.setExiledWith(exiledWith);
|
c.setExiledWith(exiledWith);
|
||||||
c.setExiledBy(exiledWith.getController());
|
c.setExiledBy(exiledWith.getController());
|
||||||
exiledWith.addExiledWith(c);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1087,13 +1074,19 @@ public abstract class GameState {
|
|||||||
Card c = entry.getKey();
|
Card c = entry.getKey();
|
||||||
List<String> colors = entry.getValue();
|
List<String> colors = entry.getValue();
|
||||||
|
|
||||||
c.setChosenColors(colors, c.getFirstSpellAbility());
|
c.setChosenColors(colors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chosen type
|
// Chosen type
|
||||||
for (Entry<Card, List<String>> entry : cardToChosenTypes.entrySet()) {
|
for (Entry<Card, String> entry : cardToChosenType.entrySet()) {
|
||||||
Card c = entry.getKey();
|
Card c = entry.getKey();
|
||||||
c.setChosenType(entry.getValue(), c.getFirstSpellAbility());
|
c.setChosenType(entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chosen type 2
|
||||||
|
for (Entry<Card, String> entry : cardToChosenType2.entrySet()) {
|
||||||
|
Card c = entry.getKey();
|
||||||
|
c.setChosenType2(entry.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Named card
|
// Named card
|
||||||
@@ -1196,7 +1189,7 @@ public abstract class GameState {
|
|||||||
String[] allCounterStrings = counterString.split(",");
|
String[] allCounterStrings = counterString.split(",");
|
||||||
for (final String counterPair : allCounterStrings) {
|
for (final String counterPair : allCounterStrings) {
|
||||||
String[] pair = counterPair.split("=", 2);
|
String[] pair = counterPair.split("=", 2);
|
||||||
entity.addCounter(CounterType.getType(pair[0]), Integer.parseInt(pair[1]), null, false, false, null);
|
entity.addCounter(CounterType.getType(pair[0]), Integer.parseInt(pair[1]), null, null, false, false, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1236,15 +1229,14 @@ public abstract class GameState {
|
|||||||
boolean tapped = c.isTapped();
|
boolean tapped = c.isTapped();
|
||||||
boolean sickness = c.hasSickness();
|
boolean sickness = c.hasSickness();
|
||||||
Map<CounterType, Integer> counters = c.getCounters();
|
Map<CounterType, Integer> counters = c.getCounters();
|
||||||
// Note: Not clearCounters() since we want to keep the counters
|
// Note: Not clearCounters() since we want to keep the counters var as-is.
|
||||||
// var as-is.
|
|
||||||
c.setCounters(Maps.newHashMap());
|
c.setCounters(Maps.newHashMap());
|
||||||
if (c.isAura()) {
|
if (c.isAura()) {
|
||||||
// dummy "enchanting" to indicate that the card will be force-attached elsewhere
|
// dummy "enchanting" to indicate that the card will be force-attached elsewhere
|
||||||
// (will be overridden later, so the actual value shouldn't matter)
|
// (will be overridden later, so the actual value shouldn't matter)
|
||||||
|
|
||||||
//FIXME it shouldn't be able to attach itself
|
//FIXME it shouldn't be able to attach itself
|
||||||
c.setEntityAttachedTo(c);
|
c.setEntityAttachedTo(CardFactory.copyCard(c, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cardsWithoutETBTrigs.contains(c)) {
|
if (cardsWithoutETBTrigs.contains(c)) {
|
||||||
@@ -1340,7 +1332,7 @@ public abstract class GameState {
|
|||||||
}
|
}
|
||||||
else if (info.startsWith("OnAdventure")) {
|
else if (info.startsWith("OnAdventure")) {
|
||||||
String abAdventure = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell";
|
String abAdventure = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell";
|
||||||
AbilitySub saAdventure = (AbilitySub)AbilityFactory.getAbility(abAdventure, c);
|
SpellAbility saAdventure = AbilityFactory.getAbility(abAdventure, c);
|
||||||
StringBuilder sbPlay = new StringBuilder();
|
StringBuilder sbPlay = new StringBuilder();
|
||||||
sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure");
|
sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure");
|
||||||
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");
|
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");
|
||||||
@@ -1379,7 +1371,9 @@ public abstract class GameState {
|
|||||||
} else if (info.startsWith("ChosenColor:")) {
|
} else if (info.startsWith("ChosenColor:")) {
|
||||||
cardToChosenClrs.put(c, Arrays.asList(info.substring(info.indexOf(':') + 1).split(",")));
|
cardToChosenClrs.put(c, Arrays.asList(info.substring(info.indexOf(':') + 1).split(",")));
|
||||||
} else if (info.startsWith("ChosenType:")) {
|
} else if (info.startsWith("ChosenType:")) {
|
||||||
cardToChosenTypes.put(c, Arrays.asList(info.substring(info.indexOf(':') + 1).split(",")));
|
cardToChosenType.put(c, info.substring(info.indexOf(':') + 1));
|
||||||
|
} else if (info.startsWith("ChosenType2:")) {
|
||||||
|
cardToChosenType2.put(c, info.substring(info.indexOf(':') + 1));
|
||||||
} else if (info.startsWith("ChosenCards:")) {
|
} else if (info.startsWith("ChosenCards:")) {
|
||||||
CardCollection chosen = new CardCollection();
|
CardCollection chosen = new CardCollection();
|
||||||
String[] idlist = info.substring(info.indexOf(':') + 1).split(",");
|
String[] idlist = info.substring(info.indexOf(':') + 1).split(",");
|
||||||
@@ -1411,6 +1405,14 @@ public abstract class GameState {
|
|||||||
}
|
}
|
||||||
} else if (info.equals("NoETBTrigs")) {
|
} else if (info.equals("NoETBTrigs")) {
|
||||||
cardsWithoutETBTrigs.add(c);
|
cardsWithoutETBTrigs.add(c);
|
||||||
|
} else if (info.equals("Foretold")) {
|
||||||
|
c.setForetold(true);
|
||||||
|
c.turnFaceDown(true);
|
||||||
|
c.addMayLookTemp(c.getOwner());
|
||||||
|
} else if (info.equals("ForetoldThisTurn")) {
|
||||||
|
c.setForetoldThisTurn(true);
|
||||||
|
} else if (info.equals("IsToken")) {
|
||||||
|
c.setToken(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
@@ -79,6 +80,7 @@ import forge.util.MyRandom;
|
|||||||
import forge.util.collect.FCollection;
|
import forge.util.collect.FCollection;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A prototype for player controller class
|
* A prototype for player controller class
|
||||||
*
|
*
|
||||||
@@ -129,6 +131,29 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
return ComputerUtilCombat.distributeAIDamage(attacker, blockers, damageDealt, defender, overrideOrder);
|
return ComputerUtilCombat.distributeAIDamage(attacker, blockers, damageDealt, defender, overrideOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<GameEntity, Integer> divideShield(Card effectSource, Map<GameEntity, Integer> affected, int shieldAmount) {
|
||||||
|
// TODO: AI current can't use this so this is not implemented.
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Byte, Integer> specifyManaCombo(SpellAbility sa, ColorSet colorSet, int manaAmount, boolean different) {
|
||||||
|
Map<Byte, Integer> result = new HashMap<>();
|
||||||
|
for (int i = 0; i < manaAmount; ++i) {
|
||||||
|
Byte chosen = chooseColor("", sa, colorSet);
|
||||||
|
if (result.containsKey(chosen)) {
|
||||||
|
result.put(chosen, result.get(chosen) + 1);
|
||||||
|
} else {
|
||||||
|
result.put(chosen, 1);
|
||||||
|
}
|
||||||
|
if (different) {
|
||||||
|
colorSet = ColorSet.fromMask(colorSet.getColor() - chosen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer announceRequirements(SpellAbility ability, String announce) {
|
public Integer announceRequirements(SpellAbility ability, String announce) {
|
||||||
// For now, these "announcements" are made within the AI classes of the appropriate SA effects
|
// For now, these "announcements" are made within the AI classes of the appropriate SA effects
|
||||||
@@ -528,7 +553,14 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CardCollectionView chooseCardsToDiscardUnlessType(int num, CardCollectionView hand, String uType, SpellAbility sa) {
|
public CardCollectionView chooseCardsToDiscardUnlessType(int num, CardCollectionView hand, String uType, SpellAbility sa) {
|
||||||
final CardCollectionView cardsOfType = CardLists.getType(hand, uType);
|
String [] splitUTypes = uType.split(",");
|
||||||
|
CardCollection cardsOfType = new CardCollection();
|
||||||
|
for (String part : splitUTypes) {
|
||||||
|
CardCollection partCards = CardLists.getType(hand, part);
|
||||||
|
if (!partCards.isEmpty()) {
|
||||||
|
cardsOfType.addAll(partCards);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!cardsOfType.isEmpty()) {
|
if (!cardsOfType.isEmpty()) {
|
||||||
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
|
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
|
||||||
return new CardCollection(toDiscard);
|
return new CardCollection(toDiscard);
|
||||||
@@ -543,12 +575,13 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> chooseSomeType(String kindOfType, SpellAbility sa, int min, int max, List<String> validTypes) {
|
public String chooseSomeType(String kindOfType, SpellAbility sa, Collection<String> validTypes, List<String> invalidTypes, boolean isOptional) {
|
||||||
List<String> chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), min, max, validTypes);
|
String chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), validTypes, invalidTypes);
|
||||||
if (chosen.isEmpty()) {
|
if (StringUtils.isBlank(chosen) && !validTypes.isEmpty()) {
|
||||||
return validTypes.subList(0, min);
|
chosen = validTypes.iterator().next();
|
||||||
|
System.err.println("AI has no idea how to choose " + kindOfType +", defaulting to arbitrary element: chosen");
|
||||||
}
|
}
|
||||||
getGame().getAction().notifyOfValue(sa, player, chosen.toString(), player);
|
getGame().getAction().notifyOfValue(sa, player, chosen, player);
|
||||||
return chosen;
|
return chosen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,8 +591,8 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) {
|
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, GameEntity affected, String question) {
|
||||||
return brains.aiShouldRun(replacementEffect, effectSA);
|
return brains.aiShouldRun(replacementEffect, effectSA, affected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -639,7 +672,6 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean playChosenSpellAbility(SpellAbility sa) {
|
public boolean playChosenSpellAbility(SpellAbility sa) {
|
||||||
// System.out.println("Playing sa: " + sa);
|
|
||||||
if (sa instanceof LandAbility) {
|
if (sa instanceof LandAbility) {
|
||||||
if (sa.canPlay()) {
|
if (sa.canPlay()) {
|
||||||
sa.resolve();
|
sa.resolve();
|
||||||
@@ -778,7 +810,8 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
case "BetterTgtThanRemembered":
|
case "BetterTgtThanRemembered":
|
||||||
if (source.getRememberedCount() > 0) {
|
if (source.getRememberedCount() > 0) {
|
||||||
Card rem = (Card) source.getFirstRemembered();
|
Card rem = (Card) source.getFirstRemembered();
|
||||||
if (!rem.isInZone(ZoneType.Battlefield)) {
|
// avoid pumping opponent creature
|
||||||
|
if (!rem.isInZone(ZoneType.Battlefield) || rem.getController().isOpponentOf(source.getController())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
for (Card c : source.getController().getCreaturesInPlay()) {
|
for (Card c : source.getController().getCreaturesInPlay()) {
|
||||||
@@ -988,6 +1021,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
emptyAbility.setActivatingPlayer(player);
|
emptyAbility.setActivatingPlayer(player);
|
||||||
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
|
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
|
||||||
emptyAbility.setSVars(sa.getSVars());
|
emptyAbility.setSVars(sa.getSVars());
|
||||||
|
emptyAbility.setXManaCostPaid(sa.getRootAbility().getXManaCostPaid());
|
||||||
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
|
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
|
||||||
ComputerUtil.playNoStack(player, emptyAbility, getGame()); // AI needs something to resolve to pay that cost
|
ComputerUtil.playNoStack(player, emptyAbility, getGame()); // AI needs something to resolve to pay that cost
|
||||||
return true;
|
return true;
|
||||||
@@ -1019,7 +1053,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
*/
|
*/
|
||||||
if (sa.isMayChooseNewTargets() && !sa.setupTargets()) {
|
if (sa.isMayChooseNewTargets() && !sa.setupTargets()) {
|
||||||
if (sa.isSpell()) {
|
if (sa.isSpell()) {
|
||||||
sa.getHostCard().ceaseToExist();
|
player.getGame().getAction().ceaseToExist(sa.getHostCard(), false);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1053,13 +1087,12 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
|
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
|
||||||
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
||||||
Spell spell = (Spell) tgtSA;
|
Spell spell = (Spell) tgtSA;
|
||||||
if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) {
|
if (tgtSA.checkRestrictions(brains.getPlayer()) && (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional)) {
|
||||||
if (noManaCost) {
|
if (noManaCost) {
|
||||||
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame());
|
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame());
|
||||||
} else {
|
}
|
||||||
return ComputerUtil.playStack(tgtSA, player, getGame());
|
return ComputerUtil.playStack(tgtSA, player, getGame());
|
||||||
}
|
}
|
||||||
} else
|
|
||||||
return false; // didn't play spell
|
return false; // didn't play spell
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -1083,7 +1116,6 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
boolean allCreatures = Iterables.all(Iterables.concat(pile1, pile2), CardPredicates.Presets.CREATURES);
|
boolean allCreatures = Iterables.all(Iterables.concat(pile1, pile2), CardPredicates.Presets.CREATURES);
|
||||||
int cmc1 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile1) : ComputerUtilCard.evaluatePermanentList(pile1);
|
int cmc1 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile1) : ComputerUtilCard.evaluatePermanentList(pile1);
|
||||||
int cmc2 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile2) : ComputerUtilCard.evaluatePermanentList(pile2);
|
int cmc2 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile2) : ComputerUtilCard.evaluatePermanentList(pile2);
|
||||||
System.out.println("value:" + cmc1 + " " + cmc2);
|
|
||||||
|
|
||||||
// for now, this assumes that the outcome will be bad
|
// for now, this assumes that the outcome will be bad
|
||||||
// TODO: This should really have a ChooseLogic param to
|
// TODO: This should really have a ChooseLogic param to
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import forge.game.ability.ApiType;
|
|||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardCollectionView;
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardFactoryUtil;
|
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
@@ -112,6 +111,63 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Brain in a Jar
|
||||||
|
public static class BrainInAJar {
|
||||||
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
|
int counterNum = source.getCounters(CounterEnumType.CHARGE);
|
||||||
|
// no need for logic
|
||||||
|
if (counterNum == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int libsize = ai.getCardsIn(ZoneType.Library).size();
|
||||||
|
|
||||||
|
final CardCollection hand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.or(
|
||||||
|
CardPredicates.isType("Instant"), CardPredicates.isType("Sorcery")));
|
||||||
|
if (!hand.isEmpty()) {
|
||||||
|
// has spell that can be cast in hand with put ability
|
||||||
|
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum + 1)).isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// has spell that can be cast if one counter is removed
|
||||||
|
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum)).isEmpty()) {
|
||||||
|
sa.setXManaCostPaid(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final CardCollection library = CardLists.filter(ai.getCardsIn(ZoneType.Library), Predicates.or(
|
||||||
|
CardPredicates.isType("Instant"), CardPredicates.isType("Sorcery")));
|
||||||
|
if (!library.isEmpty()) {
|
||||||
|
// get max cmc of instant or sorceries in the libary
|
||||||
|
int maxCMC = 0;
|
||||||
|
for (final Card c : library) {
|
||||||
|
int v = c.getCMC();
|
||||||
|
if (c.isSplitCard()) {
|
||||||
|
v = Math.max(c.getCMC(Card.SplitCMCMode.LeftSplitCMC), c.getCMC(Card.SplitCMCMode.RightSplitCMC));
|
||||||
|
}
|
||||||
|
if (v > maxCMC) {
|
||||||
|
maxCMC = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// there is a spell with more CMC, no need to remove counter
|
||||||
|
if (counterNum + 1 < maxCMC) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int maxToRemove = counterNum - maxCMC + 1;
|
||||||
|
// no Scry 0, even if its catched from later stuff
|
||||||
|
if (maxToRemove <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sa.setXManaCostPaid(maxToRemove);
|
||||||
|
} else {
|
||||||
|
// no Instant or Sorceries anymore, just scry
|
||||||
|
sa.setXManaCostPaid(Math.min(counterNum, libsize));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Chain of Acid
|
// Chain of Acid
|
||||||
public static class ChainOfAcid {
|
public static class ChainOfAcid {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
@@ -159,10 +215,9 @@ public class SpecialCardAi {
|
|||||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
final Combat combat = ai.getGame().getCombat();
|
final Combat combat = ai.getGame().getCombat();
|
||||||
|
|
||||||
Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa);
|
Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa.getSubAbility());
|
||||||
animated.addType("Creature");
|
|
||||||
if (sa.getHostCard().canReceiveCounters(CounterEnumType.P1P1)) {
|
if (sa.getHostCard().canReceiveCounters(CounterEnumType.P1P1)) {
|
||||||
animated.addCounter(CounterEnumType.P1P1, 2, ai, false, null);
|
animated.addCounter(CounterEnumType.P1P1, 2, ai, sa.getSubAbility(), false, null);
|
||||||
}
|
}
|
||||||
boolean isOppEOT = ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai;
|
boolean isOppEOT = ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai;
|
||||||
boolean isValuableAttacker = ph.is(PhaseType.MAIN1, ai) && ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animated);
|
boolean isValuableAttacker = ph.is(PhaseType.MAIN1, ai) && ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animated);
|
||||||
@@ -170,10 +225,6 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
return isOppEOT || isValuableAttacker || isValuableBlocker;
|
return isOppEOT || isValuableAttacker || isValuableBlocker;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SpellAbility considerAnimating(final Player ai, final SpellAbility sa, final List<SpellAbility> options) {
|
|
||||||
return ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) ? options.get(0) : options.get(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursed Scroll
|
// Cursed Scroll
|
||||||
@@ -327,7 +378,7 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do not activate if damage will be prevented
|
// Do not activate if damage will be prevented
|
||||||
if (source.staticDamagePrevention(predictedPT.getLeft(), source, true, true) == 0) {
|
if (source.staticDamagePrevention(predictedPT.getLeft(), 0, source, true) == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -911,6 +962,39 @@ public class SpecialCardAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Multiple Choice
|
||||||
|
public static class MultipleChoice {
|
||||||
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
|
int maxX = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
|
|
||||||
|
if (maxX == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean canScryDraw = maxX >= 1 && ai.getCardsIn(ZoneType.Library).size() >= 3; // TODO: generalize / use profile values
|
||||||
|
boolean canBounce = maxX >= 2 && !ai.getOpponents().getCreaturesInPlay().isEmpty();
|
||||||
|
boolean shouldBounce = canBounce && ComputerUtilCard.evaluateCreature(ComputerUtilCard.getWorstCreatureAI(ai.getOpponents().getCreaturesInPlay())) > 210; // 180 is the level of a 4/4 token creature
|
||||||
|
boolean canMakeToken = maxX >= 3;
|
||||||
|
boolean canDoAll = maxX >= 4 && canScryDraw && shouldBounce;
|
||||||
|
|
||||||
|
if (canDoAll) {
|
||||||
|
sa.setXManaCostPaid(4);
|
||||||
|
return true;
|
||||||
|
} else if (canMakeToken) {
|
||||||
|
sa.setXManaCostPaid(3);
|
||||||
|
return true;
|
||||||
|
} else if (shouldBounce) {
|
||||||
|
sa.setXManaCostPaid(2);
|
||||||
|
return true;
|
||||||
|
} else if (canScryDraw) {
|
||||||
|
sa.setXManaCostPaid(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Necropotence
|
// Necropotence
|
||||||
public static class Necropotence {
|
public static class Necropotence {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
@@ -1015,7 +1099,7 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
for (final SpellAbility testSa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, ai)) {
|
for (final SpellAbility testSa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, ai)) {
|
||||||
ManaCost cost = testSa.getPayCosts().getTotalMana();
|
ManaCost cost = testSa.getPayCosts().getTotalMana();
|
||||||
boolean canPayWithAvailableColors = cost.canBePaidWithAvaliable(ColorSet.fromNames(
|
boolean canPayWithAvailableColors = cost.canBePaidWithAvailable(ColorSet.fromNames(
|
||||||
ComputerUtilCost.getAvailableManaColors(ai, sa.getHostCard())).getColor());
|
ComputerUtilCost.getAvailableManaColors(ai, sa.getHostCard())).getColor());
|
||||||
|
|
||||||
byte colorProfile = cost.getColorProfile();
|
byte colorProfile = cost.getColorProfile();
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ public abstract class SpellAbilityAi {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
|
|
||||||
|
if (sa.hasParam("AICheckCanPlayWithDefinedX")) {
|
||||||
|
// FIXME: can this somehow be simplified without the need for an extra AI hint?
|
||||||
|
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
|
||||||
|
}
|
||||||
|
|
||||||
if (!checkConditions(ai, sa, sa.getConditions())) {
|
if (!checkConditions(ai, sa, sa.getConditions())) {
|
||||||
SpellAbility sub = sa.getSubAbility();
|
SpellAbility sub = sa.getSubAbility();
|
||||||
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {
|
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {
|
||||||
@@ -188,7 +193,7 @@ public abstract class SpellAbilityAi {
|
|||||||
* Handles the AI decision to play a triggered SpellAbility
|
* Handles the AI decision to play a triggered SpellAbility
|
||||||
*/
|
*/
|
||||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
if (canPlayWithoutRestrict(aiPlayer, sa)) {
|
if (canPlayWithoutRestrict(aiPlayer, sa) && (!mandatory || sa.isTargetNumberValid())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,7 +290,6 @@ public abstract class SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
|
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.BidLife, BidLifeAi.class)
|
.put(ApiType.BidLife, BidLifeAi.class)
|
||||||
.put(ApiType.Bond, BondAi.class)
|
.put(ApiType.Bond, BondAi.class)
|
||||||
.put(ApiType.Branch, AlwaysPlayAi.class)
|
.put(ApiType.Branch, AlwaysPlayAi.class)
|
||||||
|
.put(ApiType.Camouflage, ChooseCardAi.class)
|
||||||
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
|
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
|
||||||
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
||||||
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
||||||
@@ -49,6 +50,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.ChooseSource, ChooseSourceAi.class)
|
.put(ApiType.ChooseSource, ChooseSourceAi.class)
|
||||||
.put(ApiType.ChooseType, ChooseTypeAi.class)
|
.put(ApiType.ChooseType, ChooseTypeAi.class)
|
||||||
.put(ApiType.Clash, ClashAi.class)
|
.put(ApiType.Clash, ClashAi.class)
|
||||||
|
.put(ApiType.ClassLevelUp, AlwaysPlayAi.class)
|
||||||
.put(ApiType.Cleanup, AlwaysPlayAi.class)
|
.put(ApiType.Cleanup, AlwaysPlayAi.class)
|
||||||
.put(ApiType.Clone, CloneAi.class)
|
.put(ApiType.Clone, CloneAi.class)
|
||||||
.put(ApiType.CopyPermanent, CopyPermanentAi.class)
|
.put(ApiType.CopyPermanent, CopyPermanentAi.class)
|
||||||
@@ -94,8 +96,10 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.Haunt, HauntAi.class)
|
.put(ApiType.Haunt, HauntAi.class)
|
||||||
.put(ApiType.ImmediateTrigger, ImmediateTriggerAi.class)
|
.put(ApiType.ImmediateTrigger, ImmediateTriggerAi.class)
|
||||||
.put(ApiType.Investigate, InvestigateAi.class)
|
.put(ApiType.Investigate, InvestigateAi.class)
|
||||||
|
.put(ApiType.Learn, LearnAi.class)
|
||||||
.put(ApiType.LoseLife, LifeLoseAi.class)
|
.put(ApiType.LoseLife, LifeLoseAi.class)
|
||||||
.put(ApiType.LosesGame, GameLossAi.class)
|
.put(ApiType.LosesGame, GameLossAi.class)
|
||||||
|
.put(ApiType.MakeCard, AlwaysPlayAi.class)
|
||||||
.put(ApiType.Mana, ManaEffectAi.class)
|
.put(ApiType.Mana, ManaEffectAi.class)
|
||||||
.put(ApiType.ManaReflected, CannotPlayAi.class)
|
.put(ApiType.ManaReflected, CannotPlayAi.class)
|
||||||
.put(ApiType.Manifest, ManifestAi.class)
|
.put(ApiType.Manifest, ManifestAi.class)
|
||||||
@@ -139,6 +143,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.ReplaceEffect, AlwaysPlayAi.class)
|
.put(ApiType.ReplaceEffect, AlwaysPlayAi.class)
|
||||||
.put(ApiType.ReplaceDamage, AlwaysPlayAi.class)
|
.put(ApiType.ReplaceDamage, AlwaysPlayAi.class)
|
||||||
.put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class)
|
.put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class)
|
||||||
|
.put(ApiType.ReplaceToken, AlwaysPlayAi.class)
|
||||||
.put(ApiType.RestartGame, RestartGameAi.class)
|
.put(ApiType.RestartGame, RestartGameAi.class)
|
||||||
.put(ApiType.Reveal, RevealAi.class)
|
.put(ApiType.Reveal, RevealAi.class)
|
||||||
.put(ApiType.RevealHand, RevealHandAi.class)
|
.put(ApiType.RevealHand, RevealHandAi.class)
|
||||||
@@ -168,6 +173,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.UnattachAll, UnattachAllAi.class)
|
.put(ApiType.UnattachAll, UnattachAllAi.class)
|
||||||
.put(ApiType.Untap, UntapAi.class)
|
.put(ApiType.Untap, UntapAi.class)
|
||||||
.put(ApiType.UntapAll, UntapAllAi.class)
|
.put(ApiType.UntapAll, UntapAllAi.class)
|
||||||
|
.put(ApiType.Venture, VentureAi.class)
|
||||||
.put(ApiType.Vote, VoteAi.class)
|
.put(ApiType.Vote, VoteAi.class)
|
||||||
.put(ApiType.WinsGame, GameWinAi.class)
|
.put(ApiType.WinsGame, GameWinAi.class)
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ai.getStrongestOpponent();
|
||||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
|
||||||
|
|
||||||
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
@@ -40,12 +39,13 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
return randomReturn;
|
return randomReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ai.getStrongestOpponent();
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public class AmassAi extends SpellAbilityAi {
|
|||||||
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, false);
|
Card token = TokenInfo.getProtoType(tokenScript, sa, ai, false);
|
||||||
|
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
return false;
|
return false;
|
||||||
@@ -98,4 +98,3 @@ public class AmassAi extends SpellAbilityAi {
|
|||||||
return ComputerUtilCard.getBestAI(better);
|
return ComputerUtilCard.getBestAI(better);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import forge.game.cost.CostPutCounter;
|
|||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.staticability.StaticAbilityContinuous;
|
import forge.game.staticability.StaticAbilityContinuous;
|
||||||
@@ -99,30 +100,30 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
// Don't use instant speed animate abilities before AI's COMBAT_BEGIN
|
// Don't use instant speed animate abilities before AI's COMBAT_BEGIN
|
||||||
if (!ph.is(PhaseType.COMBAT_BEGIN) && ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
if (!ph.is(PhaseType.COMBAT_BEGIN) && ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
||||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
|
&& !sa.hasParam("ActivationPhases") && !"Permanent".equals(sa.getParam("Duration"))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Don't use instant speed animate abilities outside human's
|
// Don't use instant speed animate abilities outside human's
|
||||||
// COMBAT_DECLARE_ATTACKERS or if no attackers
|
// COMBAT_DECLARE_ATTACKERS or if no attackers
|
||||||
if (ph.getPlayerTurn().isOpponentOf(ai) && !sa.hasParam("Permanent")
|
if (ph.getPlayerTurn().isOpponentOf(ai) && !"Permanent".equals(sa.getParam("Duration"))
|
||||||
&& (!ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
&& (!ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| game.getCombat() != null && game.getCombat().getAttackersOf(ai).isEmpty())) {
|
|| game.getCombat() != null && game.getCombat().getAttackersOf(ai).isEmpty())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Don't activate during MAIN2 unless this effect is permanent
|
// Don't activate during MAIN2 unless this effect is permanent
|
||||||
if (ph.is(PhaseType.MAIN2) && !sa.hasParam("Permanent") && !sa.hasParam("UntilYourNextTurn")) {
|
if (ph.is(PhaseType.MAIN2) && !"Permanent".equals(sa.getParam("Duration")) && !"UntilYourNextTurn".equals(sa.getParam("Duration"))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Don't animate if the AI won't attack anyway or use as a potential blocker
|
// Don't animate if the AI won't attack anyway or use as a potential blocker
|
||||||
Player opponent = ai.getWeakestOpponent();
|
Player opponent = ai.getWeakestOpponent();
|
||||||
// Activating as a potential blocker is only viable if it's an ability activated from a permanent, otherwise
|
// Activating as a potential blocker is only viable if it's an ability activated from a permanent, otherwise
|
||||||
// the AI will waste resources
|
// the AI will waste resources
|
||||||
boolean activateAsPotentialBlocker = sa.hasParam("UntilYourNextTurn")
|
boolean activateAsPotentialBlocker = "UntilYourNextTurn".equals(sa.getParam("Duration"))
|
||||||
&& ai.getGame().getPhaseHandler().getNextTurn() != ai
|
&& ai.getGame().getPhaseHandler().getNextTurn() != ai
|
||||||
&& source.isPermanent();
|
&& source.isPermanent();
|
||||||
if (ph.isPlayerTurn(ai) && ai.getLife() < 6 && opponent.getLife() > 6
|
if (ph.isPlayerTurn(ai) && ai.getLife() < 6 && opponent.getLife() > 6
|
||||||
&& Iterables.any(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)
|
&& Iterables.any(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)
|
||||||
&& !sa.hasParam("AILogic") && !sa.hasParam("Permanent") && !activateAsPotentialBlocker) {
|
&& !sa.hasParam("AILogic") && !"Permanent".equals(sa.getParam("Duration")) && !activateAsPotentialBlocker) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -155,7 +156,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
&& !c.isEquipping();
|
&& !c.isEquipping();
|
||||||
|
|
||||||
// for creatures that could be improved (like Figure of Destiny)
|
// for creatures that could be improved (like Figure of Destiny)
|
||||||
if (!bFlag && c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
|
if (!bFlag && c.isCreature() && ("Permanent".equals(sa.getParam("Duration")) || (!c.isTapped() && !c.isSick()))) {
|
||||||
int power = -5;
|
int power = -5;
|
||||||
if (sa.hasParam("Power")) {
|
if (sa.hasParam("Power")) {
|
||||||
power = AbilityUtils.calculateAmount(c, sa.getParam("Power"), sa);
|
power = AbilityUtils.calculateAmount(c, sa.getParam("Power"), sa);
|
||||||
@@ -178,7 +179,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SpellAbilityAi.isSorcerySpeed(sa) && !sa.hasParam("Permanent")) {
|
if (!SpellAbilityAi.isSorcerySpeed(sa) && !"Permanent".equals(sa.getParam("Duration"))) {
|
||||||
if (sa.hasParam("Crew") && c.isCreature()) {
|
if (sa.hasParam("Crew") && c.isCreature()) {
|
||||||
// Do not try to crew a vehicle which is already a creature
|
// Do not try to crew a vehicle which is already a creature
|
||||||
return false;
|
return false;
|
||||||
@@ -243,10 +244,15 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
|
return player.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean animateTgtAI(final SpellAbility sa) {
|
private boolean animateTgtAI(final SpellAbility sa) {
|
||||||
final Player ai = sa.getActivatingPlayer();
|
final Player ai = sa.getActivatingPlayer();
|
||||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
final boolean alwaysActivatePWAbility = sa.hasParam("Planeswalker")
|
final boolean alwaysActivatePWAbility = sa.isPwAbility()
|
||||||
&& 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;
|
||||||
@@ -272,7 +278,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
Map<Card, Integer> data = Maps.newHashMap();
|
Map<Card, Integer> data = Maps.newHashMap();
|
||||||
for (final Card c : list) {
|
for (final Card c : list) {
|
||||||
// don't use Permanent animate on something that would leave the field
|
// don't use Permanent animate on something that would leave the field
|
||||||
if (c.hasSVar("EndOfTurnLeavePlay") && sa.hasParam("Permanent")) {
|
if (c.hasSVar("EndOfTurnLeavePlay") && "Permanent".equals(sa.getParam("Duration"))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,9 +312,9 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
// if its player turn,
|
// if its player turn,
|
||||||
// check if its Permanent or that creature would attack
|
// check if its Permanent or that creature would attack
|
||||||
if (ph.isPlayerTurn(ai)) {
|
if (ph.isPlayerTurn(ai)) {
|
||||||
if (!sa.hasParam("Permanent")
|
if (!"Permanent".equals(sa.getParam("Duration"))
|
||||||
&& !ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animatedCopy)
|
&& !ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animatedCopy)
|
||||||
&& !sa.hasParam("UntilHostLeavesPlay")) {
|
&& !"UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,8 +360,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
|
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
|
||||||
// two are the only things
|
// two are the only things
|
||||||
// that animate a target. Those can just use AI:RemoveDeck:All until
|
// that animate a target. Those can just use AI:RemoveDeck:All until
|
||||||
// this can do a reasonably
|
// this can do a reasonably good job of picking a good target
|
||||||
// good job of picking a good target
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,7 +406,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
// allow ChosenType - overrides anything else specified
|
// allow ChosenType - overrides anything else specified
|
||||||
if (types.hasSubtype("ChosenType")) {
|
if (types.hasSubtype("ChosenType")) {
|
||||||
types.clear();
|
types.clear();
|
||||||
types.addAll(sa.getChosenType());
|
types.add(source.getChosenType());
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<String> keywords = Lists.newArrayList();
|
final List<String> keywords = Lists.newArrayList();
|
||||||
@@ -432,7 +437,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
if (sa.hasParam("Colors")) {
|
if (sa.hasParam("Colors")) {
|
||||||
final String colors = sa.getParam("Colors");
|
final String colors = sa.getParam("Colors");
|
||||||
if (colors.equals("ChosenColor")) {
|
if (colors.equals("ChosenColor")) {
|
||||||
tmpDesc = CardUtil.getShortColorsString(sa.getChosenColors());
|
tmpDesc = CardUtil.getShortColorsString(source.getChosenColors());
|
||||||
} else {
|
} else {
|
||||||
tmpDesc = CardUtil.getShortColorsString(Lists.newArrayList(Arrays.asList(colors.split(","))));
|
tmpDesc = CardUtil.getShortColorsString(Lists.newArrayList(Arrays.asList(colors.split(","))));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
@@ -114,8 +115,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (abCost.getTotalMana().countX() > 0 && sa.getSVar("X").equals("Count$xPaid")) {
|
if (abCost.getTotalMana().countX() > 0 && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value. (Endless Scream and Venarian
|
// Set PayX here to maximum value. (Endless Scream and Venarian Gold)
|
||||||
// Gold)
|
|
||||||
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
|
|
||||||
if (xPay == 0) {
|
if (xPay == 0) {
|
||||||
@@ -554,12 +554,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
if (!evenBetterList.isEmpty()) {
|
if (!evenBetterList.isEmpty()) {
|
||||||
betterList = evenBetterList;
|
betterList = evenBetterList;
|
||||||
}
|
}
|
||||||
evenBetterList = CardLists.filter(betterList, new Predicate<Card>() {
|
evenBetterList = CardLists.filter(betterList, CardPredicates.Presets.UNTAPPED);
|
||||||
@Override
|
|
||||||
public boolean apply(final Card c) {
|
|
||||||
return c.isUntapped();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!evenBetterList.isEmpty()) {
|
if (!evenBetterList.isEmpty()) {
|
||||||
betterList = evenBetterList;
|
betterList = evenBetterList;
|
||||||
}
|
}
|
||||||
@@ -733,17 +728,15 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
private static Card attachAISpecificCardPreference(final SpellAbility sa, final List<Card> list, final boolean mandatory,
|
private static Card attachAISpecificCardPreference(final SpellAbility sa, final List<Card> list, final boolean mandatory,
|
||||||
final Card attachSource) {
|
final Card attachSource) {
|
||||||
// I know this isn't much better than Hardcoding, but some cards need it for now
|
|
||||||
final Player ai = sa.getActivatingPlayer();
|
final Player ai = sa.getActivatingPlayer();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
Card chosen = null;
|
Card chosen = null;
|
||||||
|
|
||||||
if ("Guilty Conscience".equals(sourceName)) {
|
if ("Guilty Conscience".equals(sourceName)) {
|
||||||
chosen = SpecialCardAi.GuiltyConscience.getBestAttachTarget(ai, sa, list);
|
chosen = SpecialCardAi.GuiltyConscience.getBestAttachTarget(ai, sa, list);
|
||||||
} else if ("Bonds of Faith".equals(sourceName)) {
|
} else if (sa.hasParam("AIValid")) {
|
||||||
chosen = doPumpOrCurseAILogic(ai, sa, list, "Human");
|
// TODO: Make the AI recognize which cards to pump based on the card's abilities alone
|
||||||
} else if ("Clutch of Undeath".equals(sourceName)) {
|
chosen = doPumpOrCurseAILogic(ai, sa, list, sa.getParam("AIValid"));
|
||||||
chosen = doPumpOrCurseAILogic(ai, sa, list, "Zombie");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If Mandatory (brought directly into play without casting) gotta
|
// If Mandatory (brought directly into play without casting) gotta
|
||||||
@@ -767,7 +760,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
int powerBuff = 0;
|
int powerBuff = 0;
|
||||||
for (StaticAbility stAb : sa.getHostCard().getStaticAbilities()) {
|
for (StaticAbility stAb : sa.getHostCard().getStaticAbilities()) {
|
||||||
if ("Card.EquippedBy".equals(stAb.getParam("Affected")) && stAb.hasParam("AddPower")) {
|
if ("Card.EquippedBy".equals(stAb.getParam("Affected")) && stAb.hasParam("AddPower")) {
|
||||||
powerBuff = AbilityUtils.calculateAmount(sa.getHostCard(), stAb.getParam("AddPower"), null);
|
powerBuff = AbilityUtils.calculateAmount(sa.getHostCard(), stAb.getParam("AddPower"), stAb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (combat != null && combat.isAttacking(equipped) && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS, sa.getActivatingPlayer())) {
|
if (combat != null && combat.isAttacking(equipped) && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS, sa.getActivatingPlayer())) {
|
||||||
@@ -990,7 +983,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
List<GameObject> targets = new ArrayList<>();
|
List<GameObject> targets = new ArrayList<>();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
targets = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
targets = AbilityUtils.getDefinedObjects(card, sa.getParam("Defined"), sa);
|
||||||
} else {
|
} else {
|
||||||
AttachAi.attachPreference(sa, tgt, mandatory);
|
AttachAi.attachPreference(sa, tgt, mandatory);
|
||||||
targets = sa.getTargets();
|
targets = sa.getTargets();
|
||||||
@@ -1129,8 +1122,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
for (Card target : list) {
|
for (Card target : list) {
|
||||||
for (Trigger t : target.getTriggers()) {
|
for (Trigger t : target.getTriggers()) {
|
||||||
if (t.getMode() == TriggerType.SpellCast) {
|
if (t.getMode() == TriggerType.SpellCast) {
|
||||||
final Map<String, String> params = t.getMapParams();
|
if ("Card.Self".equals(t.getParam("TargetsValid")) && "You".equals(t.getParam("ValidActivatingPlayer"))) {
|
||||||
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))) {
|
|
||||||
magnetList.add(target);
|
magnetList.add(target);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1351,7 +1343,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
CardCollection list = null;
|
CardCollection list = null;
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
list = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
list = AbilityUtils.getDefinedCards(attachSource, sa.getParam("Defined"), sa);
|
||||||
} else {
|
} else {
|
||||||
list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(tgt.getZone()), tgt.getValidTgts(), sa.getActivatingPlayer(), attachSource, sa);
|
list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(tgt.getZone()), tgt.getValidTgts(), sa.getActivatingPlayer(), attachSource, sa);
|
||||||
|
|
||||||
@@ -1646,7 +1638,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1;
|
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1;
|
||||||
} else if (keyword.endsWith("CARDNAME attacks each turn if able.") || keyword.endsWith("CARDNAME attacks each combat if able.")) {
|
} else if (keyword.endsWith("CARDNAME attacks each turn if able.") || keyword.endsWith("CARDNAME attacks each combat if able.")) {
|
||||||
return ComputerUtilCombat.canAttackNextTurn(card) && CombatUtil.canBlock(card, true) && !ai.getCreaturesInPlay().isEmpty();
|
return ComputerUtilCombat.canAttackNextTurn(card) && CombatUtil.canBlock(card, true) && !ai.getCreaturesInPlay().isEmpty();
|
||||||
} else if (keyword.endsWith("CARDNAME can't block.") || keyword.contains("CantBlock")) {
|
} else if (keyword.endsWith("CARDNAME can't block.")) {
|
||||||
return CombatUtil.canBlock(card, true);
|
return CombatUtil.canBlock(card, true);
|
||||||
} else if (keyword.endsWith("CARDNAME's activated abilities can't be activated.")) {
|
} else if (keyword.endsWith("CARDNAME's activated abilities can't be activated.")) {
|
||||||
for (SpellAbility ability : card.getSpellAbilities()) {
|
for (SpellAbility ability : card.getSpellAbilities()) {
|
||||||
@@ -1699,7 +1691,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
if (!c.getController().equals(ai)) {
|
if (!c.getController().equals(ai)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return c.getType().hasCreatureType(type);
|
return c.isValid(type, ai, sa.getHostCard(), sa);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
List<Card> oppNonType = CardLists.filter(list, new Predicate<Card>() {
|
List<Card> oppNonType = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@@ -1709,7 +1701,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
if (c.getController().equals(ai)) {
|
if (c.getController().equals(ai)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return !c.getType().hasCreatureType(type) && !ComputerUtilCard.isUselessCreature(ai, c);
|
return !c.isValid(type, ai, sa.getHostCard(), sa) && !ComputerUtilCard.isUselessCreature(ai, c);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,15 +13,18 @@ public class BalanceAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
|
|
||||||
int diff = 0;
|
int diff = 0;
|
||||||
// TODO Add support for multiplayer logic
|
Player opp = aiPlayer.getWeakestOpponent();
|
||||||
final Player opp = aiPlayer.getWeakestOpponent();
|
|
||||||
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
|
||||||
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||||
|
for (Player min : aiPlayer.getOpponents()) {
|
||||||
|
if (min.getCardsIn(ZoneType.Battlefield).size() < opp.getCardsIn(ZoneType.Battlefield).size()) {
|
||||||
|
opp = min;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
||||||
|
|
||||||
if ("BalanceCreaturesAndLands".equals(logic)) {
|
if ("BalanceCreaturesAndLands".equals(logic)) {
|
||||||
// Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting
|
// TODO Copied over from hardcoded Balance. We should be checking value of the lands/creatures for each opponent, not just counting
|
||||||
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
|
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
|
||||||
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
|
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
|
||||||
diff += 1.5 * (CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
|
diff += 1.5 * (CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -24,7 +25,7 @@ public class BidLifeAi extends SpellAbilityAi {
|
|||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (tgt.canTgtCreature()) {
|
if (tgt.canTgtCreature()) {
|
||||||
List<Card> list = CardLists.getTargetableCards(aiPlayer.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), sa);
|
List<Card> list = CardLists.getTargetableCards(AiAttackController.choosePreferredDefenderPlayer(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ public final class BondAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
} // end bondCanPlayAI()
|
} // end bondCanPlayAI()
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||||
return ComputerUtilCard.getBestCreatureAI(options);
|
return ComputerUtilCard.getBestCreatureAI(options);
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ public class CanPlayAsDrawbackAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
|
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells,
|
||||||
Map<String, Object> params) {
|
Map<String, Object> params) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import forge.game.card.Card;
|
|||||||
import forge.game.mana.ManaCostBeingPaid;
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetChoices;
|
||||||
|
|
||||||
public class ChangeTargetsAi extends SpellAbilityAi {
|
public class ChangeTargetsAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -40,18 +41,20 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
|||||||
// nothing on stack, so nothing to target
|
// nothing on stack, so nothing to target
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
final TargetChoices topTargets = topSa.getTargets();
|
||||||
|
final Card topHost = topSa.getHostCard();
|
||||||
|
|
||||||
if (sa.getTargets().size() != 0) {
|
if (sa.getTargets().size() != 0 && sa.isTrigger()) {
|
||||||
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
|
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!topSa.usesTargeting() || topSa.getTargets().getTargetCards().contains(sa.getHostCard())) {
|
if (!topSa.usesTargeting() || topTargets.getTargetCards().contains(sa.getHostCard())) {
|
||||||
// if this does not target at all or already targets host, no need to redirect it again
|
// if this does not target at all or already targets host, no need to redirect it again
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Card tgt : topSa.getTargets().getTargetCards()) {
|
for (Card tgt : topTargets.getTargetCards()) {
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) {
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) {
|
||||||
// We are already targeting at least one card with the same name (e.g. in presence of 2+ Spellskites),
|
// We are already targeting at least one card with the same name (e.g. in presence of 2+ Spellskites),
|
||||||
// no need to retarget again to another one
|
// no need to retarget again to another one
|
||||||
@@ -59,7 +62,7 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topSa.getHostCard() != null && !topSa.getHostCard().getController().isOpponentOf(aiPlayer)) {
|
if (topHost != null && !topHost.getController().isOpponentOf(aiPlayer)) {
|
||||||
// make sure not to redirect our own abilities
|
// make sure not to redirect our own abilities
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -80,12 +83,22 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
|||||||
ManaCost normalizedMana = manaCost.getNormalizedMana();
|
ManaCost normalizedMana = manaCost.getNormalizedMana();
|
||||||
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
|
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
|
||||||
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
|
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
|
||||||
&& topSa.getTargets().contains(aiPlayer)) {
|
&& topTargets.contains(aiPlayer)) {
|
||||||
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
|
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Card firstCard = topTargets.getFirstTargetedCard();
|
||||||
|
// if we're not the target don't intervene unless we can steal a buff
|
||||||
|
if (firstCard != null && !aiPlayer.equals(firstCard.getController()) && !topHost.getController().equals(firstCard.getController()) && !topHost.getController().getAllies().contains(firstCard.getController())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Player firstPlayer = topTargets.getFirstTargetedPlayer();
|
||||||
|
if (firstPlayer != null && !aiPlayer.equals(firstPlayer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(topSa);
|
sa.getTargets().add(topSa);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import com.google.common.collect.Iterables;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.AiBlockController;
|
import forge.ai.AiBlockController;
|
||||||
import forge.ai.AiCardMemory;
|
import forge.ai.AiCardMemory;
|
||||||
import forge.ai.AiController;
|
import forge.ai.AiController;
|
||||||
@@ -57,6 +58,7 @@ import forge.game.player.PlayerActionConfirmMode;
|
|||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
@@ -74,11 +76,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
Card host = sa.getHostCard();
|
if (sa.getHostCard() != null && sa.getHostCard().hasSVar("AIPreferenceOverride")) {
|
||||||
|
|
||||||
if (host != null && host.hasSVar("AIPreferenceOverride")) {
|
|
||||||
// currently used by SacAndUpgrade logic, might need simplification
|
// currently used by SacAndUpgrade logic, might need simplification
|
||||||
host.removeSVar("AIPreferenceOverride");
|
sa.getHostCard().removeSVar("AIPreferenceOverride");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aiLogic.equals("BeforeCombat")) {
|
if (aiLogic.equals("BeforeCombat")) {
|
||||||
@@ -92,7 +92,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
} else if (aiLogic.equals("PriorityOptionalCost")) {
|
} else if (aiLogic.equals("PriorityOptionalCost")) {
|
||||||
boolean highPriority = false;
|
boolean highPriority = false;
|
||||||
// if we have more than one of these in hand, might not be worth waiting for optional cost payment on the additional copy
|
// if we have more than one of these in hand, might not be worth waiting for optional cost payment on the additional copy
|
||||||
highPriority |= CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.nameEquals(host.getName())).size() > 1;
|
highPriority |= CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.nameEquals(sa.getHostCard().getName())).size() > 1;
|
||||||
// if we are in danger in combat, no need to wait to pay the optional cost
|
// if we are in danger in combat, no need to wait to pay the optional cost
|
||||||
highPriority |= ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
highPriority |= ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
&& ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInDanger(ai, ai.getGame().getCombat());
|
&& ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInDanger(ai, ai.getGame().getCombat());
|
||||||
@@ -127,40 +127,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
} else if (aiLogic.equals("Pongify")) {
|
} else if (aiLogic.equals("Pongify")) {
|
||||||
return SpecialAiLogic.doPongifyLogic(ai, sa);
|
return SpecialAiLogic.doPongifyLogic(ai, sa);
|
||||||
} else if (aiLogic.equals("Ashiok")) {
|
|
||||||
final int loyalty = host.getCurrentLoyalty() - 1;
|
|
||||||
CardCollectionView choices = new CardCollection();
|
|
||||||
for (int i = loyalty; i >= 0; i--) {
|
|
||||||
sa.setXManaCostPaid(i);
|
|
||||||
choices = ai.getGame().getCardsIn(ZoneType.listValueOf(sa.getParam("Origin")));
|
|
||||||
choices = CardLists.getValidCards(choices, sa.getParam("ChangeType"), host.getController(), host, sa);
|
|
||||||
if (!choices.isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else if (aiLogic.equals("BestCard")) {
|
|
||||||
CardCollectionView choices = ai.getGame().getCardsIn(ZoneType.listValueOf(sa.getParam("Origin")));
|
|
||||||
choices = CardLists.getValidCards(choices, sa.getParam("ChangeType"), host.getController(), host, sa);
|
|
||||||
if (!choices.isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (aiLogic.startsWith("DiscardAllAndRetExiled")) {
|
|
||||||
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(host)).size();
|
|
||||||
int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
|
||||||
|
|
||||||
// minimum card advantage unless the hand will be fully reloaded
|
|
||||||
int minAdv = aiLogic.contains(".minAdv") ? Integer.parseInt(aiLogic.substring(aiLogic.indexOf(".minAdv") + 7)) : 0;
|
|
||||||
|
|
||||||
if (numExiledWithSrc > curHandSize) {
|
|
||||||
if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(host)) {
|
|
||||||
// Try to gain some card advantage if the card will die anyway
|
|
||||||
// TODO: ideally, should evaluate the hand value and not discard good hands to it
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (curHandSize + minAdv - 1 < numExiledWithSrc) || (numExiledWithSrc >= ai.getMaxHandSize());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.checkAiLogic(ai, sa, aiLogic);
|
return super.checkAiLogic(ai, sa, aiLogic);
|
||||||
@@ -197,16 +163,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return SpecialCardAi.MazesEnd.consider(aiPlayer, sa);
|
return SpecialCardAi.MazesEnd.consider(aiPlayer, sa);
|
||||||
} else if (aiLogic.equals("Pongify")) {
|
} else if (aiLogic.equals("Pongify")) {
|
||||||
return sa.isTargetNumberValid(); // Pre-targeted in checkAiLogic
|
return sa.isTargetNumberValid(); // Pre-targeted in checkAiLogic
|
||||||
} else if (aiLogic.equals("Ashiok")) {
|
|
||||||
return true; // If checkAiLogic returns true, then we should be good to go
|
|
||||||
} else if (aiLogic.equals("BestCard")) {
|
|
||||||
return true; // If checkAiLogic returns true, then we should be good to go
|
|
||||||
} else if (aiLogic.startsWith("DiscardAllAndRetExiled")) {
|
|
||||||
return true; // If checkAiLogic returns true, then we should be good to go
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (sa.isHidden()) {
|
||||||
if (isHidden(sa)) {
|
|
||||||
return hiddenOriginCanPlayAI(aiPlayer, sa);
|
return hiddenOriginCanPlayAI(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
return knownOriginCanPlayAI(aiPlayer, sa);
|
return knownOriginCanPlayAI(aiPlayer, sa);
|
||||||
@@ -223,21 +182,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
if (isHidden(sa)) {
|
if (sa.isHidden()) {
|
||||||
return hiddenOriginPlayDrawbackAI(aiPlayer, sa);
|
return hiddenOriginPlayDrawbackAI(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
return knownOriginPlayDrawbackAI(aiPlayer, sa);
|
return knownOriginPlayDrawbackAI(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static boolean isHidden(SpellAbility sa) {
|
|
||||||
boolean hidden = sa.hasParam("Hidden");
|
|
||||||
if (!hidden && sa.hasParam("Origin")) {
|
|
||||||
hidden = ZoneType.isHidden(sa.getParam("Origin"));
|
|
||||||
}
|
|
||||||
return hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* changeZoneTriggerAINoCost.
|
* changeZoneTriggerAINoCost.
|
||||||
@@ -273,7 +223,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return delta <= 0;
|
return delta <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHidden(sa)) {
|
if (sa.isHidden()) {
|
||||||
return hiddenTriggerAI(aiPlayer, sa, mandatory);
|
return hiddenTriggerAI(aiPlayer, sa, mandatory);
|
||||||
}
|
}
|
||||||
return knownOriginTriggerAI(aiPlayer, sa, mandatory);
|
return knownOriginTriggerAI(aiPlayer, sa, mandatory);
|
||||||
@@ -306,7 +256,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
ZoneType origin = null;
|
ZoneType origin = null;
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||||
|
|
||||||
if (sa.hasParam("Origin")) {
|
if (sa.hasParam("Origin")) {
|
||||||
@@ -457,6 +407,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (num.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
if (num.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
|
if (xPay == 0) return false;
|
||||||
xPay = Math.min(xPay, list.size());
|
xPay = Math.min(xPay, list.size());
|
||||||
sa.setXManaCostPaid(xPay);
|
sa.setXManaCostPaid(xPay);
|
||||||
}
|
}
|
||||||
@@ -514,7 +465,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
// if putting cards from hand to library and parent is drawing cards
|
// if putting cards from hand to library and parent is drawing cards
|
||||||
// make sure this will actually do something:
|
// make sure this will actually do something:
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Player opp = aiPlayer.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||||
if (tgt != null && tgt.canTgtPlayer()) {
|
if (tgt != null && tgt.canTgtPlayer()) {
|
||||||
boolean isCurse = sa.isCurse();
|
boolean isCurse = sa.isCurse();
|
||||||
if (isCurse && sa.canTarget(opp)) {
|
if (isCurse && sa.canTarget(opp)) {
|
||||||
@@ -573,7 +524,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
Iterable<Player> pDefined;
|
Iterable<Player> pDefined;
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if ((tgt != null) && tgt.canTgtPlayer()) {
|
if ((tgt != null) && tgt.canTgtPlayer()) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
@@ -829,7 +780,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHidden(sa)) {
|
if (sa.isHidden()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -935,7 +886,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
||||||
sa.setXManaCostPaid(xPay);
|
sa.setXManaCostPaid(xPay);
|
||||||
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
|
|
||||||
}
|
}
|
||||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
||||||
|
|
||||||
@@ -956,9 +906,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (sa.isSpell()) {
|
if (sa.isSpell()) {
|
||||||
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
||||||
}
|
}
|
||||||
//System.out.println("isPreferredTarget " + list);
|
|
||||||
if (sa.hasParam("AttachedTo")) {
|
if (sa.hasParam("AttachedTo")) {
|
||||||
//System.out.println("isPreferredTarget att " + list);
|
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
@@ -970,7 +918,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
//System.out.println("isPreferredTarget ok " + list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.size() < sa.getMinTargets()) {
|
if (list.size() < sa.getMinTargets()) {
|
||||||
@@ -1183,7 +1130,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean doWithoutTarget = sa.hasParam("Planeswalker") && sa.usesTargeting()
|
boolean doWithoutTarget = sa.isPwAbility() && sa.usesTargeting()
|
||||||
&& sa.getMinTargets() == 0
|
&& sa.getMinTargets() == 0
|
||||||
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class);
|
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class);
|
||||||
|
|
||||||
@@ -1206,6 +1153,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
if (destination.equals(ZoneType.Battlefield) || origin.contains(ZoneType.Battlefield)) {
|
if (destination.equals(ZoneType.Battlefield) || origin.contains(ZoneType.Battlefield)) {
|
||||||
|
// filter by MustTarget requirement
|
||||||
|
CardCollection originalList = new CardCollection(list);
|
||||||
|
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
|
||||||
|
|
||||||
final Card mostExpensive = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false);
|
final Card mostExpensive = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false);
|
||||||
if (mostExpensive.isCreature()) {
|
if (mostExpensive.isCreature()) {
|
||||||
// if a creature is most expensive take the best one
|
// if a creature is most expensive take the best one
|
||||||
@@ -1234,6 +1185,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore original list for next loop if filtered by MustTarget requirement
|
||||||
|
if (mustTargetFiltered) {
|
||||||
|
list = originalList;
|
||||||
|
}
|
||||||
} else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) {
|
} else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) {
|
||||||
List<Card> nonLands = CardLists.getNotType(list, "Land");
|
List<Card> nonLands = CardLists.getNotType(list, "Land");
|
||||||
// Prefer to pull a creature, generally more useful for AI.
|
// Prefer to pull a creature, generally more useful for AI.
|
||||||
@@ -1352,8 +1308,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
private static Card canBouncePermanent(final Player ai, SpellAbility sa, CardCollectionView list) {
|
private static Card canBouncePermanent(final Player ai, SpellAbility sa, CardCollectionView list) {
|
||||||
Game game = ai.getGame();
|
Game game = ai.getGame();
|
||||||
// filter out untargetables
|
// filter out untargetables
|
||||||
CardCollectionView aiPermanents = CardLists
|
CardCollectionView aiPermanents = CardLists.filterControlledBy(list, ai);
|
||||||
.filterControlledBy(list, ai);
|
|
||||||
CardCollection aiPlaneswalkers = CardLists.filter(aiPermanents, Presets.PLANESWALKERS);
|
CardCollection aiPlaneswalkers = CardLists.filter(aiPermanents, Presets.PLANESWALKERS);
|
||||||
|
|
||||||
// Felidar Guardian + Saheeli Rai combo support
|
// Felidar Guardian + Saheeli Rai combo support
|
||||||
@@ -1525,9 +1480,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if ("DeathgorgeScavenger".equals(logic)) {
|
if ("DeathgorgeScavenger".equals(logic)) {
|
||||||
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
|
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
|
||||||
} else if ("ExtraplanarLens".equals(logic)) {
|
}
|
||||||
|
if ("ExtraplanarLens".equals(logic)) {
|
||||||
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
|
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
|
||||||
} else if ("ExileCombatThreat".equals(logic)) {
|
}
|
||||||
|
if ("ExileCombatThreat".equals(logic)) {
|
||||||
return doExileCombatThreatLogic(ai, sa);
|
return doExileCombatThreatLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1753,7 +1710,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
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
|
||||||
@@ -1904,6 +1860,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!data.isEmpty()) {
|
||||||
// JAVA 1.8 use Map.Entry.comparingByValue() somehow
|
// JAVA 1.8 use Map.Entry.comparingByValue() somehow
|
||||||
Map.Entry<Player, Map.Entry<String, Integer>> max = Collections.max(data.entrySet(), new Comparator<Map.Entry<Player, Map.Entry<String, Integer>>>() {
|
Map.Entry<Player, Map.Entry<String, Integer>> max = Collections.max(data.entrySet(), new Comparator<Map.Entry<Player, Map.Entry<String, Integer>>>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -1922,6 +1879,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2021,17 +1979,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
boolean setPayX = false;
|
boolean setPayX = false;
|
||||||
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||||
setPayX = true;
|
setPayX = true;
|
||||||
// TODO use ComputerUtilCost.getMaxXValue if able
|
toPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
|
||||||
} else {
|
} else {
|
||||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toPay == 0) {
|
if (toPay == 0 || toPay <= usableManaSources) {
|
||||||
canBeSaved.add(potentialTgt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toPay <= usableManaSources) {
|
|
||||||
canBeSaved.add(potentialTgt);
|
canBeSaved.add(potentialTgt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -245,8 +245,25 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
&& !ComputerUtil.isPlayingReanimator(ai);
|
&& !ComputerUtil.isPlayingReanimator(ai);
|
||||||
}
|
}
|
||||||
} else if (origin.equals(ZoneType.Exile)) {
|
} else if (origin.equals(ZoneType.Exile)) {
|
||||||
// TODO: nothing to do here at the moment
|
String logic = sa.getParam("AILogic");
|
||||||
return false;
|
|
||||||
|
if (logic != null && logic.startsWith("DiscardAllAndRetExiled")) {
|
||||||
|
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(source)).size();
|
||||||
|
int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
|
||||||
|
|
||||||
|
// minimum card advantage unless the hand will be fully reloaded
|
||||||
|
int minAdv = logic.contains(".minAdv") ? Integer.parseInt(logic.substring(logic.indexOf(".minAdv") + 7)) : 0;
|
||||||
|
|
||||||
|
if (numExiledWithSrc > curHandSize) {
|
||||||
|
if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(source)) {
|
||||||
|
// Try to gain some card advantage if the card will die anyway
|
||||||
|
// TODO: ideally, should evaluate the hand value and not discard good hands to it
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (curHandSize + minAdv - 1 < numExiledWithSrc) || (numExiledWithSrc >= ai.getMaxHandSize());
|
||||||
|
}
|
||||||
} else if (origin.equals(ZoneType.Stack)) {
|
} else if (origin.equals(ZoneType.Stack)) {
|
||||||
// time stop can do something like this:
|
// time stop can do something like this:
|
||||||
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
|
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
|
||||||
|
|||||||
@@ -23,21 +23,22 @@ import forge.util.collect.FCollection;
|
|||||||
public class CharmAi extends SpellAbilityAi {
|
public class CharmAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
// sa is Entwined, no need for extra logic
|
|
||||||
if (sa.isEntwine()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
|
||||||
|
|
||||||
final int num = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("CharmNum", "1"), sa);
|
final int num;
|
||||||
final int min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParamOrDefault("MinCharmNum", "1"), sa) : num;
|
final int min;
|
||||||
|
if (sa.isEntwine()) {
|
||||||
|
num = min = choices.size();
|
||||||
|
} else {
|
||||||
|
num = AbilityUtils.calculateAmount(source, sa.getParamOrDefault("CharmNum", "1"), sa);
|
||||||
|
min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParamOrDefault("MinCharmNum", "1"), sa) : num;
|
||||||
|
}
|
||||||
|
|
||||||
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
|
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
|
||||||
|
|
||||||
// Reset the chosen list otherwise it will be locked in forever by earlier calls
|
// Reset the chosen list otherwise it will be locked in forever by earlier calls
|
||||||
sa.setChosenList(null);
|
sa.setChosenList(null);
|
||||||
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
|
|
||||||
List<AbilitySub> chosenList;
|
List<AbilitySub> chosenList;
|
||||||
|
|
||||||
if (!ai.equals(sa.getActivatingPlayer())) {
|
if (!ai.equals(sa.getActivatingPlayer())) {
|
||||||
@@ -159,7 +160,7 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
chosenList.add(allyTainted ? gain : lose);
|
chosenList.add(allyTainted ? gain : lose);
|
||||||
} else if (oppTainted || ai.getGame().isCardInPlay("Rain of Gore")) {
|
} else if (oppTainted || ai.getGame().isCardInPlay("Rain of Gore")) {
|
||||||
// Rain of Gore does negate lifegain, so don't benefit the others
|
// Rain of Gore does negate lifegain, so don't benefit the others
|
||||||
// same for if a oppoent does control Tainted Remedy
|
// same for if a opponent does control Tainted Remedy
|
||||||
// but if ai cant gain life, the effects are negated
|
// but if ai cant gain life, the effects are negated
|
||||||
chosenList.add(ai.canGainLife() ? lose : gain);
|
chosenList.add(ai.canGainLife() ? lose : gain);
|
||||||
} else if (ai.getGame().isCardInPlay("Sulfuric Vortex")) {
|
} else if (ai.getGame().isCardInPlay("Sulfuric Vortex")) {
|
||||||
@@ -177,13 +178,13 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
chosenList.add(gain);
|
chosenList.add(gain);
|
||||||
} else if(!ai.canGainLife() && aiLife == 14 ) {
|
} else if(!ai.canGainLife() && aiLife == 14 ) {
|
||||||
// ai cant gain life, but try to avoid falling to 13
|
// ai cant gain life, but try to avoid falling to 13
|
||||||
// but if a oppoent does control Tainted Remedy its irrelevant
|
// but if a opponent does control Tainted Remedy its irrelevant
|
||||||
chosenList.add(oppTainted ? lose : gain);
|
chosenList.add(oppTainted ? lose : gain);
|
||||||
} else if (allyTainted) {
|
} else if (allyTainted) {
|
||||||
// Tainted Remedy negation logic, try gain instead of lose
|
// Tainted Remedy negation logic, try gain instead of lose
|
||||||
// because negation does turn it into lose for opponents
|
// because negation does turn it into lose for opponents
|
||||||
boolean oppCritical = false;
|
boolean oppCritical = false;
|
||||||
// an oppoent is Critical = 14, and can't gain life, try to lose life instead
|
// an opponent is Critical = 14, and can't gain life, try to lose life instead
|
||||||
// but only if ai doesn't kill itself with that.
|
// but only if ai doesn't kill itself with that.
|
||||||
if (aiLife != 14) {
|
if (aiLife != 14) {
|
||||||
for (Player p : opponents) {
|
for (Player p : opponents) {
|
||||||
@@ -197,7 +198,7 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
} else {
|
} else {
|
||||||
// normal logic, try to gain life if its critical
|
// normal logic, try to gain life if its critical
|
||||||
boolean oppCritical = false;
|
boolean oppCritical = false;
|
||||||
// an oppoent is Critical = 12, and can gain life, try to gain life instead
|
// an opponent is Critical = 12, and can gain life, try to gain life instead
|
||||||
// but only if ai doesn't kill itself with that.
|
// but only if ai doesn't kill itself with that.
|
||||||
if (aiLife != 12) {
|
if (aiLife != 12) {
|
||||||
for (Player p : opponents) {
|
for (Player p : opponents) {
|
||||||
@@ -224,6 +225,8 @@ public class CharmAi extends SpellAbilityAi {
|
|||||||
goodChoice = sub;
|
goodChoice = sub;
|
||||||
} else {
|
} else {
|
||||||
// Standard canPlayAi()
|
// Standard canPlayAi()
|
||||||
|
sub.setActivatingPlayer(ai);
|
||||||
|
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
|
||||||
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
|
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
|
||||||
chosenList.add(sub);
|
chosenList.add(sub);
|
||||||
if (chosenList.size() == min) {
|
if (chosenList.size() == min) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.google.common.base.Predicates;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
@@ -20,6 +21,7 @@ import forge.game.card.CardCollectionView;
|
|||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CardPredicates.Presets;
|
import forge.game.card.CardPredicates.Presets;
|
||||||
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
@@ -73,10 +75,8 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
return !choices.isEmpty();
|
return !choices.isEmpty();
|
||||||
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
|
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
|
||||||
return choices.size() >= 2;
|
return choices.size() >= 2;
|
||||||
} else if (aiLogic.equals("Clone") || aiLogic.equals("Vesuva")) {
|
} else if (aiLogic.equals("Clone")) {
|
||||||
final String filter = aiLogic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
|
||||||
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
|
||||||
|
|
||||||
choices = CardLists.getValidCards(choices, filter, host.getController(), host, sa);
|
choices = CardLists.getValidCards(choices, filter, host.getController(), host, sa);
|
||||||
return !choices.isEmpty();
|
return !choices.isEmpty();
|
||||||
} else if (aiLogic.equals("Never")) {
|
} else if (aiLogic.equals("Never")) {
|
||||||
@@ -96,12 +96,24 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
|
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return !choices.isEmpty();
|
||||||
|
} else if (aiLogic.equals("Ashiok")) {
|
||||||
|
final int loyalty = host.getCounters(CounterEnumType.LOYALTY) - 1;
|
||||||
|
for (int i = loyalty; i >= 0; i--) {
|
||||||
|
sa.setXManaCostPaid(i);
|
||||||
|
choices = ai.getGame().getCardsIn(choiceZone);
|
||||||
|
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host, sa);
|
||||||
|
if (!choices.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return !choices.isEmpty();
|
return !choices.isEmpty();
|
||||||
} else if (aiLogic.equals("RandomNonLand")) {
|
} else if (aiLogic.equals("RandomNonLand")) {
|
||||||
return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host, sa).isEmpty();
|
return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host, sa).isEmpty();
|
||||||
} else if (aiLogic.equals("Duneblast")) {
|
} else if (aiLogic.equals("Duneblast")) {
|
||||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||||
CardCollection oppCreatures = ai.getWeakestOpponent().getCreaturesInPlay();
|
CardCollection oppCreatures = AiAttackController.choosePreferredDefenderPlayer(ai).getCreaturesInPlay();
|
||||||
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
|
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
|
||||||
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
|
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
|
||||||
|
|
||||||
@@ -159,18 +171,13 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
options = CardLists.filter(options, Presets.UNTAPPED);
|
options = CardLists.filter(options, Presets.UNTAPPED);
|
||||||
}
|
}
|
||||||
choice = ComputerUtilCard.getBestCreatureAI(options);
|
choice = ComputerUtilCard.getBestCreatureAI(options);
|
||||||
} else if (logic.equals("Clone") || logic.equals("Vesuva")) {
|
} else if (logic.equals("Clone")) {
|
||||||
final String filter = logic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary" :
|
final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
|
||||||
"Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
|
||||||
|
|
||||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||||
if (!newOptions.isEmpty()) {
|
if (!newOptions.isEmpty()) {
|
||||||
options = newOptions;
|
options = newOptions;
|
||||||
}
|
}
|
||||||
choice = ComputerUtilCard.getBestAI(options);
|
choice = ComputerUtilCard.getBestAI(options);
|
||||||
if (logic.equals("Vesuva") && "Vesuva".equals(choice.getName())) {
|
|
||||||
choice = null;
|
|
||||||
}
|
|
||||||
} else if ("RandomNonLand".equals(logic)) {
|
} else if ("RandomNonLand".equals(logic)) {
|
||||||
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host, sa);
|
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host, sa);
|
||||||
choice = Aggregates.random(options);
|
choice = Aggregates.random(options);
|
||||||
@@ -239,7 +246,6 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
System.out.println("Tangle Wire" + options + " - " + betterList);
|
|
||||||
if (!betterList.isEmpty()) {
|
if (!betterList.isEmpty()) {
|
||||||
choice = betterList.get(0);
|
choice = betterList.get(0);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.google.common.collect.Iterables;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpecialCardAi;
|
import forge.ai.SpecialCardAi;
|
||||||
@@ -34,9 +35,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
if (logic.equals("MomirAvatar")) {
|
if (logic.equals("CursedScroll")) {
|
||||||
return SpecialCardAi.MomirVigAvatar.consider(ai, sa);
|
|
||||||
} else if (logic.equals("CursedScroll")) {
|
|
||||||
return SpecialCardAi.CursedScroll.consider(ai, sa);
|
return SpecialCardAi.CursedScroll.consider(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +43,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
|||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (tgt.canOnlyTgtOpponent()) {
|
if (tgt.canOnlyTgtOpponent()) {
|
||||||
sa.getTargets().add(ai.getWeakestOpponent());
|
sa.getTargets().add(AiAttackController.choosePreferredDefenderPlayer(ai));
|
||||||
} else {
|
} else {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
}
|
}
|
||||||
@@ -64,7 +63,6 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -16,7 +17,7 @@ public class ChooseEvenOddAi extends SpellAbilityAi {
|
|||||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
Player opp = aiPlayer.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import com.google.common.collect.Iterables;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
|
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
@@ -32,7 +31,6 @@ import forge.game.phase.PhaseType;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.staticability.StaticAbilityCantBeCast;
|
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.collect.FCollection;
|
import forge.util.collect.FCollection;
|
||||||
@@ -54,8 +52,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else if ("GideonBlackblade".equals(aiLogic)) {
|
} else if ("GideonBlackblade".equals(aiLogic)) {
|
||||||
return SpecialCardAi.GideonBlackblade.consider(ai, sa);
|
return SpecialCardAi.GideonBlackblade.consider(ai, sa);
|
||||||
} else if ("SoulEcho".equals(aiLogic)) {
|
|
||||||
return doTriggerAINoCost(ai, sa, true);
|
|
||||||
} else if ("Always".equals(aiLogic)) {
|
} else if ("Always".equals(aiLogic)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -72,7 +68,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
return checkApiLogic(aiPlayer, sa);
|
return sa.isTrigger() ? doTriggerAINoCost(aiPlayer, sa, sa.isMandatory()) : checkApiLogic(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -160,6 +156,22 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return others;
|
return others;
|
||||||
|
} else if ("Counters".equals(logic)) {
|
||||||
|
// TODO: this code will need generalization if this logic is used for cards other
|
||||||
|
// than Elspeth Conquers Death with different choice parameters
|
||||||
|
SpellAbility p1p1 = null, loyalty = null;
|
||||||
|
for (final SpellAbility sp : spells) {
|
||||||
|
if (("P1P1").equals(sp.getParam("CounterType"))) {
|
||||||
|
p1p1 = sp;
|
||||||
|
} else {
|
||||||
|
loyalty = sp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sa.getParent().getTargetCard() != null && sa.getParent().getTargetCard().getType().isPlaneswalker()) {
|
||||||
|
return loyalty;
|
||||||
|
} else {
|
||||||
|
return p1p1;
|
||||||
|
}
|
||||||
} else if ("Fatespinner".equals(logic)) {
|
} else if ("Fatespinner".equals(logic)) {
|
||||||
SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null;
|
SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null;
|
||||||
for (final SpellAbility sp : spells) {
|
for (final SpellAbility sp : spells) {
|
||||||
@@ -230,11 +242,12 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
return allow;
|
return allow;
|
||||||
}
|
}
|
||||||
|
|
||||||
SpellAbility firstSpell = imprinted.getFirstSpellAbility();
|
//if Iona does prevent from casting, allow it to draw
|
||||||
// check if something would prevent it from casting
|
for (final Card io : player.getCardsIn(ZoneType.Battlefield, "Iona, Shield of Emeria")) {
|
||||||
if (firstSpell == null || StaticAbilityCantBeCast.cantBeCastAbility(firstSpell, imprinted, owner)) {
|
if (CardUtil.getColors(imprinted).hasAnyColor(MagicColor.fromName(io.getChosenColor()))) {
|
||||||
return allow;
|
return allow;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (dmg == 0) {
|
if (dmg == 0) {
|
||||||
// If CMC = 0, mill it!
|
// If CMC = 0, mill it!
|
||||||
@@ -364,8 +377,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
} else if ("Riot".equals(logic)) {
|
} else if ("Riot".equals(logic)) {
|
||||||
SpellAbility counterSA = spells.get(0), hasteSA = spells.get(1);
|
SpellAbility counterSA = spells.get(0), hasteSA = spells.get(1);
|
||||||
return preferHasteForRiot(sa, player) ? hasteSA : counterSA;
|
return preferHasteForRiot(sa, player) ? hasteSA : counterSA;
|
||||||
} else if ("CrawlingBarrens".equals(logic)) {
|
|
||||||
return SpecialCardAi.CrawlingBarrens.considerAnimating(player, sa, spells);
|
|
||||||
}
|
}
|
||||||
return spells.get(0); // return first choice if no logic found
|
return spells.get(0); // return first choice if no logic found
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -16,7 +17,7 @@ public class ChooseNumberAi extends SpellAbilityAi {
|
|||||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
Player opp = aiPlayer.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.google.common.base.Predicates;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
@@ -54,7 +55,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public class ChooseTypeAi extends SpellAbilityAi {
|
|||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
|
||||||
return doMirrorEntityLogic(aiPlayer, sa);
|
return doMirrorEntityLogic(aiPlayer, sa);
|
||||||
}
|
}
|
||||||
|
return !chooseType(sa, aiPlayer.getCardsIn(ZoneType.Battlefield)).isEmpty();
|
||||||
} else if ("MostProminentOppControls".equals(sa.getParam("AILogic"))) {
|
} else if ("MostProminentOppControls".equals(sa.getParam("AILogic"))) {
|
||||||
return !chooseType(sa, aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield)).isEmpty();
|
return !chooseType(sa, aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield)).isEmpty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ public class ClashAi extends SpellAbilityAi {
|
|||||||
return legalAction;
|
return legalAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3,13 +3,17 @@ package forge.ai.ability;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicates;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -52,7 +56,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));
|
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));
|
||||||
|
|
||||||
// for creatures that could be improved (like Figure of Destiny)
|
// for creatures that could be improved (like Figure of Destiny)
|
||||||
if (c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
|
if (c.isCreature() && (!sa.hasParam("Duration") || (!c.isTapped() && !c.isSick()))) {
|
||||||
int power = -5;
|
int power = -5;
|
||||||
if (sa.hasParam("Power")) {
|
if (sa.hasParam("Power")) {
|
||||||
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
|
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
|
||||||
@@ -68,8 +72,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bFlag) { // All of the defined stuff is cloned, not very
|
if (!bFlag) { // All of the defined stuff is cloned, not very useful
|
||||||
// useful
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -95,11 +98,18 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
Card host = sa.getHostCard();
|
||||||
boolean chance = true;
|
boolean chance = true;
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
chance = cloneTgtAI(sa);
|
chance = cloneTgtAI(sa);
|
||||||
|
} else {
|
||||||
|
if (sa.hasParam("Choices")) {
|
||||||
|
CardCollectionView choices = CardLists.getValidCards(host.getGame().getCardsIn(ZoneType.Battlefield),
|
||||||
|
sa.getParam("Choices"), host.getController(), host, sa);
|
||||||
|
|
||||||
|
chance = !choices.isEmpty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Improve AI for triggers. If source is a creature with:
|
// Improve AI for triggers. If source is a creature with:
|
||||||
@@ -171,18 +181,18 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||||
Player targetedPlayer, Map<String, Object> params) {
|
Player targetedPlayer, Map<String, Object> params) {
|
||||||
|
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
|
final String name = host.getName();
|
||||||
final Player ctrl = host.getController();
|
final Player ctrl = host.getController();
|
||||||
|
|
||||||
final Card cloneTarget = getCloneTarget(sa);
|
final Card cloneTarget = getCloneTarget(sa);
|
||||||
final boolean isOpp = cloneTarget.getController().isOpponentOf(sa.getActivatingPlayer());
|
final boolean isOpp = cloneTarget.getController().isOpponentOf(sa.getActivatingPlayer());
|
||||||
|
|
||||||
final boolean isVesuva = "Vesuva".equals(host.getName());
|
final boolean isVesuva = "Vesuva".equals(name) || "Sculpting Steel".equals(name);
|
||||||
final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary"));
|
final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary"));
|
||||||
|
|
||||||
String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
||||||
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
: "Permanent.YouDontCtrl+notnamed" + name + ",Permanent.nonLegendary+notnamed" + name;
|
||||||
|
|
||||||
// TODO: rewrite this block so that this is done somehow more elegantly
|
// TODO: rewrite this block so that this is done somehow more elegantly
|
||||||
if (canCloneLegendary) {
|
if (canCloneLegendary) {
|
||||||
@@ -201,12 +211,13 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options);
|
// prevent loop of choosing copy of same card
|
||||||
|
if (isVesuva) {
|
||||||
if (isVesuva && "Vesuva".equals(choice.getName())) {
|
options = CardLists.filter(options, Predicates.not(CardPredicates.sharesNameWith(host)));
|
||||||
choice = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options);
|
||||||
|
|
||||||
return choice;
|
return choice;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +245,7 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
// Combat_Begin step
|
// Combat_Begin step
|
||||||
if (!ph.is(PhaseType.COMBAT_BEGIN)
|
if (!ph.is(PhaseType.COMBAT_BEGIN)
|
||||||
&& ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
&& ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
||||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
|
&& !sa.hasParam("ActivationPhases") && sa.hasParam("Duration")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,6 +256,6 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// don't activate during main2 unless this effect is permanent
|
// don't activate during main2 unless this effect is permanent
|
||||||
return !ph.is(PhaseType.MAIN2) || sa.hasParam("Permanent");
|
return !ph.is(PhaseType.MAIN2) || !sa.hasParam("Duration");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package forge.ai.ability;
|
|||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
@@ -29,9 +30,8 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
CardCollection list =
|
CardCollection list =
|
||||||
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
CardLists.getValidCards(AiAttackController.choosePreferredDefenderPlayer(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||||
// AI won't try to grab cards that are filtered out of AI decks on
|
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
||||||
// purpose
|
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import forge.game.player.PlayerCollection;
|
|||||||
import forge.game.player.PlayerPredicates;
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
|
|
||||||
@@ -67,7 +68,6 @@ import forge.util.Aggregates;
|
|||||||
public class ControlGainAi extends SpellAbilityAi {
|
public class ControlGainAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||||
|
|
||||||
final List<String> lose = Lists.newArrayList();
|
final List<String> lose = Lists.newArrayList();
|
||||||
|
|
||||||
if (sa.hasParam("LoseControl")) {
|
if (sa.hasParam("LoseControl")) {
|
||||||
@@ -109,8 +109,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't steal something if I can't Attack without, or prevent it from
|
// Don't steal something if I can't Attack without, or prevent it from blocking at least
|
||||||
// blocking at least
|
|
||||||
if (lose.contains("EOT")
|
if (lose.contains("EOT")
|
||||||
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
&& !sa.isTrigger()) {
|
&& !sa.isTrigger()) {
|
||||||
@@ -210,7 +209,12 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO check life of controller and consider stealing from another opponent so the risk of your army disappearing is spread out
|
||||||
while (t == null) {
|
while (t == null) {
|
||||||
|
// filter by MustTarget requirement
|
||||||
|
CardCollection originalList = new CardCollection(list);
|
||||||
|
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
|
||||||
|
|
||||||
if (planeswalkers > 0) {
|
if (planeswalkers > 0) {
|
||||||
t = ComputerUtilCard.getBestPlaneswalkerAI(list);
|
t = ComputerUtilCard.getBestPlaneswalkerAI(list);
|
||||||
} else if (creatures > 0) {
|
} else if (creatures > 0) {
|
||||||
@@ -238,6 +242,11 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
enchantments--;
|
enchantments--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore original list for next loop if filtered by MustTarget requirement
|
||||||
|
if (mustTargetFiltered) {
|
||||||
|
list = originalList;
|
||||||
|
}
|
||||||
|
|
||||||
if (!sa.canTarget(t)) {
|
if (!sa.canTarget(t)) {
|
||||||
list.remove(t);
|
list.remove(t);
|
||||||
t = null;
|
t = null;
|
||||||
@@ -254,7 +263,6 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("MimicVat".equals(aiLogic)) {
|
if ("MomirAvatar".equals(aiLogic)) {
|
||||||
|
return SpecialCardAi.MomirVigAvatar.consider(aiPlayer, sa);
|
||||||
|
} else if ("MimicVat".equals(aiLogic)) {
|
||||||
return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
|
return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
|
||||||
} else if ("AtEOT".equals(aiLogic)) {
|
} else if ("AtEOT".equals(aiLogic)) {
|
||||||
return ph.is(PhaseType.END_OF_TURN);
|
return ph.is(PhaseType.END_OF_TURN);
|
||||||
@@ -57,7 +59,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) {
|
if (sa.hasParam("AtEOT") && !ph.is(PhaseType.MAIN1)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +118,6 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
final boolean canCopyLegendary = sa.hasParam("NonLegendary");
|
final boolean canCopyLegendary = sa.hasParam("NonLegendary");
|
||||||
|
|
||||||
|
|
||||||
// ////
|
// ////
|
||||||
// Targeting
|
// Targeting
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
|
|
||||||
final SpellAbility topSA = ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
|
final SpellAbility topSA = ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
|
||||||
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai
|
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai
|
||||||
|| ai.getAllies().contains(topSA.getActivatingPlayer())) {
|
|| ai.getAllies().contains(topSA.getActivatingPlayer())) {
|
||||||
@@ -113,8 +112,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
boolean setPayX = false;
|
boolean setPayX = false;
|
||||||
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
|
||||||
setPayX = true;
|
setPayX = true;
|
||||||
// TODO use ComputerUtilCost.getMaxXValue
|
toPay = Math.min(ComputerUtilCost.getMaxXValue(sa, ai), usableManaSources + 1);
|
||||||
toPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), usableManaSources + 1);
|
|
||||||
} else {
|
} else {
|
||||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||||
}
|
}
|
||||||
@@ -124,8 +122,7 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (toPay <= usableManaSources) {
|
if (toPay <= usableManaSources) {
|
||||||
// If this is a reusable Resource, feel free to play it most of
|
// If this is a reusable Resource, feel free to play it most of the time
|
||||||
// the time
|
|
||||||
if (!SpellAbilityAi.playReusable(ai,sa)) {
|
if (!SpellAbilityAi.playReusable(ai,sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import forge.util.collect.FCollection;
|
|||||||
public class CountersMoveAi extends SpellAbilityAi {
|
public class CountersMoveAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!moveTgtAI(ai, sa)) {
|
if (!moveTgtAI(ai, sa)) {
|
||||||
@@ -83,8 +82,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// something you can't block, try to reduce its
|
// something you can't block, try to reduce its attack
|
||||||
// attack
|
|
||||||
if (!ComputerUtilCard.canBeBlockedProfitably(ai, cpy)) {
|
if (!ComputerUtilCard.canBeBlockedProfitably(ai, cpy)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -119,7 +117,6 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
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();
|
sa.resetTargets();
|
||||||
|
|
||||||
@@ -237,7 +234,6 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean moveTgtAI(final Player ai, final SpellAbility sa) {
|
private boolean moveTgtAI(final Player ai, final SpellAbility sa) {
|
||||||
|
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final String type = sa.getParam("CounterType");
|
final String type = sa.getParam("CounterType");
|
||||||
@@ -283,8 +279,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
// cant use substract on Copy
|
// cant use substract on Copy
|
||||||
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
|
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
|
||||||
|
|
||||||
// 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 (cType != null && cType.is(CounterEnumType.P1P1) && srcCardCpy.getNetToughness() <= 0) {
|
if (cType != null && cType.is(CounterEnumType.P1P1) && 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();
|
||||||
}
|
}
|
||||||
@@ -372,6 +367,14 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Card lki = CardUtil.getLKICopy(src);
|
||||||
|
if (cType == null) {
|
||||||
|
lki.clearCounters();
|
||||||
|
} else {
|
||||||
|
lki.setCounters(cType, 0);
|
||||||
|
}
|
||||||
|
// go for opponent when higher value implies debuff
|
||||||
|
if (ComputerUtilCard.evaluateCreature(src) > ComputerUtilCard.evaluateCreature(lki)) {
|
||||||
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
|
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
|
||||||
if (!aiList.isEmpty()) {
|
if (!aiList.isEmpty()) {
|
||||||
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
|
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
|
||||||
@@ -392,7 +395,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
if (cType.is(CounterEnumType.P1P1) && card.hasKeyword(Keyword.UNDYING)) {
|
if (cType.is(CounterEnumType.P1P1) && card.hasKeyword(Keyword.UNDYING)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (cType.is(CounterEnumType.M1M1) && card.hasKeyword(Keyword.PERSIST)) {
|
if (cType.is(CounterEnumType.M1M1)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,11 +418,16 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
final boolean isMandatoryTrigger = (sa.isTrigger() && !sa.isOptionalTrigger())
|
||||||
|
|| (sa.getRootAbility().isTrigger() && !sa.getRootAbility().isOptionalTrigger());
|
||||||
|
if (!isMandatoryTrigger) {
|
||||||
|
// no good target
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// move counter to opponents creature but only if you can not steal
|
// move counter to opponents creature but only if you can not steal them
|
||||||
// them
|
// try to move to something useless or something that would leave play
|
||||||
// try to move to something useless or something that would leave
|
|
||||||
// play
|
|
||||||
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
|
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
|
||||||
if (!oppList.isEmpty()) {
|
if (!oppList.isEmpty()) {
|
||||||
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
|
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
|
||||||
@@ -441,7 +449,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (best.isEmpty()) {
|
if (best.isEmpty()) {
|
||||||
best = aiList;
|
best = oppList;
|
||||||
}
|
}
|
||||||
|
|
||||||
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
||||||
@@ -455,7 +463,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// used for multiple sources -> defied
|
// used for multiple sources -> defined
|
||||||
// or for source -> multiple defined
|
// or for source -> multiple defined
|
||||||
@Override
|
@Override
|
||||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||||
|
|||||||
@@ -120,8 +120,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
|
||||||
// pre filter targetable cards with counters and can receive one of
|
// pre filter targetable cards with counters and can receive one of them
|
||||||
// them
|
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiProps;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.PlayerControllerAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -121,6 +123,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
final CounterType poison = CounterType.get(CounterEnumType.POISON);
|
final CounterType poison = CounterType.get(CounterEnumType.POISON);
|
||||||
|
|
||||||
|
boolean aggroAI = (((PlayerControllerAi) ai.getController()).getAi()).getBooleanProperty(AiProps.PLAY_AGGRO);
|
||||||
// because countertype can't be chosen anymore, only look for posion counters
|
// because countertype can't be chosen anymore, only look for posion counters
|
||||||
for (final Player p : Iterables.filter(options, Player.class)) {
|
for (final Player p : Iterables.filter(options, Player.class)) {
|
||||||
if (p.isOpponentOf(ai)) {
|
if (p.isOpponentOf(ai)) {
|
||||||
@@ -128,7 +131,8 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
return (T)p;
|
return (T)p;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (p.getCounters(poison) <= 5 || p.canReceiveCounters(poison)) {
|
// poison is risky, should not proliferate them in most cases
|
||||||
|
if ((p.getCounters(poison) <= 5 && aggroAI && p.getCounters(CounterEnumType.EXPERIENCE) + p.getCounters(CounterEnumType.ENERGY) >= 1) || !p.canReceiveCounters(poison)) {
|
||||||
return (T)p;
|
return (T)p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ import forge.game.player.PlayerCollection;
|
|||||||
import forge.game.player.PlayerPredicates;
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.trigger.Trigger;
|
||||||
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
@@ -62,11 +64,9 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
|
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
|
||||||
|
|
||||||
final String type = sa.getParam("CounterType");
|
final String type = sa.getParam("CounterType");
|
||||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
if (!super.willPayCosts(ai, sa, cost, source)) {
|
if (!super.willPayCosts(ai, sa, cost, source)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -223,8 +223,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sa.canTarget(ai)) {
|
if (sa.canTarget(ai)) {
|
||||||
// don't target itself when its forced to add poison
|
// don't target itself when its forced to add poison counters too
|
||||||
// counters too
|
|
||||||
if (!ai.getCounters().isEmpty()) {
|
if (!ai.getCounters().isEmpty()) {
|
||||||
if (!eachExisting || ai.getPoisonCounters() < 5) {
|
if (!eachExisting || ai.getPoisonCounters() < 5) {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
@@ -313,7 +312,12 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
} else if (logic.startsWith("MoveCounter")) {
|
} else if (logic.startsWith("MoveCounter")) {
|
||||||
return doMoveCounterLogic(ai, sa, ph);
|
return doMoveCounterLogic(ai, sa, ph);
|
||||||
} else if (logic.equals("CrawlingBarrens")) {
|
} else if (logic.equals("CrawlingBarrens")) {
|
||||||
return SpecialCardAi.CrawlingBarrens.consider(ai, sa);
|
boolean willActivate = SpecialCardAi.CrawlingBarrens.consider(ai, sa);
|
||||||
|
if (willActivate && ph.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||||
|
// don't use this for mana until after combat
|
||||||
|
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
|
||||||
|
}
|
||||||
|
return willActivate;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sa.metConditions() && sa.getSubAbility() == null) {
|
if (!sa.metConditions() && sa.getSubAbility() == null) {
|
||||||
@@ -401,19 +405,37 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("Polukranos".equals(logic)) {
|
if ("Polukranos".equals(logic)) {
|
||||||
|
boolean found = false;
|
||||||
|
for (Trigger tr : source.getTriggers()) {
|
||||||
|
if (!tr.getMode().equals(TriggerType.BecomeMonstrous)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SpellAbility oa = tr.ensureAbility();
|
||||||
|
if (oa == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
CardCollection targets = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
|
// need to set Activating player
|
||||||
|
oa.setActivatingPlayer(ai);
|
||||||
|
CardCollection targets = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), oa);
|
||||||
|
|
||||||
if (!targets.isEmpty()){
|
if (!targets.isEmpty()){
|
||||||
boolean canSurvive = false;
|
boolean canSurvive = false;
|
||||||
for (Card humanCreature : targets) {
|
for (Card humanCreature : targets) {
|
||||||
if (!FightAi.canKill(humanCreature, source, 0)){
|
if (!FightAi.canKill(humanCreature, source, 0)){
|
||||||
canSurvive = true;
|
canSurvive = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!canSurvive){
|
if (!canSurvive){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,7 +477,6 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
|
|
||||||
// don't put the counter on the dead creature
|
// don't put the counter on the dead creature
|
||||||
if (sacSelf && c.equals(source)) {
|
if (sacSelf && c.equals(source)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -468,6 +489,8 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
Card sacTarget = ComputerUtil.getCardPreference(ai, source, "SacCost", list);
|
Card sacTarget = ComputerUtil.getCardPreference(ai, source, "SacCost", list);
|
||||||
// this card is planned to be sacrificed during cost payment, so don't target it
|
// this card is planned to be sacrificed during cost payment, so don't target it
|
||||||
// (otherwise the AI can cheat by activating this SA and not paying the sac cost, e.g. Extruder)
|
// (otherwise the AI can cheat by activating this SA and not paying the sac cost, e.g. Extruder)
|
||||||
|
// TODO needs update if amount > 1 gets printed,
|
||||||
|
// maybe also check putting the counter on that exact creature is more important than sacrificing it (though unlikely?)
|
||||||
list.remove(sacTarget);
|
list.remove(sacTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,7 +501,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
// Activate +Loyalty planeswalker abilities even if they have no target (e.g. Vivien of the Arkbow),
|
// Activate +Loyalty planeswalker abilities even if they have no target (e.g. Vivien of the Arkbow),
|
||||||
// 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.isPwAbility()
|
||||||
&& sa.getPayCosts().hasOnlySpecificCostType(CostPutCounter.class)
|
&& sa.getPayCosts().hasOnlySpecificCostType(CostPutCounter.class)
|
||||||
&& sa.isTargetNumberValid()
|
&& sa.isTargetNumberValid()
|
||||||
&& sa.getTargets().size() == 0
|
&& sa.getTargets().size() == 0
|
||||||
@@ -684,8 +707,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if ((!sa.isTargetNumberValid())
|
if ((!sa.isTargetNumberValid()) || (sa.getTargets().size() == 0)) {
|
||||||
|| (sa.getTargets().size() == 0)) {
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -841,6 +863,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
choice = Aggregates.random(list);
|
choice = Aggregates.random(list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (choice != null && divided) {
|
if (choice != null && divided) {
|
||||||
int alloc = Math.max(amount / totalTargets, 1);
|
int alloc = Math.max(amount / totalTargets, 1);
|
||||||
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
|
if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
|
||||||
@@ -850,8 +873,6 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
left -= alloc;
|
left -= alloc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (choice != null) {
|
if (choice != null) {
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
list.remove(choice);
|
list.remove(choice);
|
||||||
|
|||||||
@@ -195,7 +195,6 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
||||||
|
|
||||||
if (options.size() > 1) {
|
if (options.size() > 1) {
|
||||||
final Player ai = sa.getActivatingPlayer();
|
final Player ai = sa.getActivatingPlayer();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
|
|
||||||
final String type = sa.getParam("CounterType");
|
final String type = sa.getParam("CounterType");
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import forge.game.phase.PhaseType;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerDamageDone;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
|||||||
boolean dmgByCardsInHand = false;
|
boolean dmgByCardsInHand = false;
|
||||||
|
|
||||||
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&
|
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&
|
||||||
sa.getHostCard().getSVar(sa.getParam("NumDmg")).equals("TargetedPlayer$CardsInHand")) {
|
sa.getSVar(sa.getParam("NumDmg")).equals("TargetedPlayer$CardsInHand")) {
|
||||||
dmgByCardsInHand = true;
|
dmgByCardsInHand = true;
|
||||||
}
|
}
|
||||||
// Not sure if type choice implemented for the AI yet but it should at least recognize this spell hits harder on larger enemy hand size
|
// Not sure if type choice implemented for the AI yet but it should at least recognize this spell hits harder on larger enemy hand size
|
||||||
@@ -75,7 +75,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
|||||||
// If has triggered ability on dealing damage to an opponent, go for it!
|
// If has triggered ability on dealing damage to an opponent, go for it!
|
||||||
Card hostcard = sa.getHostCard();
|
Card hostcard = sa.getHostCard();
|
||||||
for (Trigger trig : hostcard.getTriggers()) {
|
for (Trigger trig : hostcard.getTriggers()) {
|
||||||
if (trig instanceof TriggerDamageDone) {
|
if (trig.getMode() == TriggerType.DamageDone) {
|
||||||
if (("Opponent".equals(trig.getParam("ValidTarget")))
|
if (("Opponent".equals(trig.getParam("ValidTarget")))
|
||||||
&& (!"True".equals(trig.getParam("CombatDamage")))) {
|
&& (!"True".equals(trig.getParam("CombatDamage")))) {
|
||||||
return true;
|
return true;
|
||||||
@@ -111,8 +111,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
|||||||
final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand);
|
final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand);
|
||||||
|
|
||||||
if ((enemy.getLife() - restDamage) < 5) {
|
if ((enemy.getLife() - restDamage) < 5) {
|
||||||
// drop the human to less than 5
|
// drop the human to less than 5 life
|
||||||
// life
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
int x = -1;
|
int x = -1;
|
||||||
final String damage = sa.getParam("NumDmg");
|
final String damage = sa.getParam("NumDmg");
|
||||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
|
||||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$Converge")) {
|
if (damage.equals("X") && sa.getSVar(damage).equals("Count$Converge")) {
|
||||||
dmg = ComputerUtilMana.getConvergeCount(sa, ai);
|
dmg = ComputerUtilMana.getConvergeCount(sa, ai);
|
||||||
}
|
}
|
||||||
@@ -202,7 +202,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
|
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
sa.setXManaCostPaid(dmg);
|
sa.setXManaCostPaid(dmg);
|
||||||
} else {
|
} else {
|
||||||
dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
dmg = AbilityUtils.calculateAmount(source, damage, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("ValidPlayers")) {
|
if (sa.hasParam("ValidPlayers")) {
|
||||||
@@ -286,7 +286,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
|||||||
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
|
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
sa.setXManaCostPaid(dmg);
|
sa.setXManaCostPaid(dmg);
|
||||||
} else {
|
} else {
|
||||||
dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
dmg = AbilityUtils.calculateAmount(source, damage, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("ValidPlayers")) {
|
if (sa.hasParam("ValidPlayers")) {
|
||||||
|
|||||||
@@ -30,22 +30,21 @@ import forge.game.ability.AbilityUtils;
|
|||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardFactoryUtil;
|
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.cost.CostPartMana;
|
import forge.game.cost.CostPartMana;
|
||||||
import forge.game.cost.CostRemoveCounter;
|
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerPredicates;
|
import forge.game.player.PlayerCollection;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetChoices;
|
import forge.game.spellability.TargetChoices;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
@@ -54,10 +53,9 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
final String damage = sa.getParam("NumDmg");
|
final String damage = sa.getParam("NumDmg");
|
||||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
|
||||||
final String logic = sa.getParam("AILogic");
|
|
||||||
|
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
|
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
|
||||||
|
final String logic = sa.getParam("AILogic");
|
||||||
|
|
||||||
if ("MadSarkhanDigDmg".equals(logic)) {
|
if ("MadSarkhanDigDmg".equals(logic)) {
|
||||||
return SpecialCardAi.SarkhanTheMad.considerDig(ai, sa);
|
return SpecialCardAi.SarkhanTheMad.considerDig(ai, sa);
|
||||||
@@ -102,13 +100,12 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
|
|
||||||
final String damage = sa.getParam("NumDmg");
|
final String damage = sa.getParam("NumDmg");
|
||||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
|
||||||
|
|
||||||
if (damage.equals("X")) {
|
if (damage.equals("X")) {
|
||||||
if (sa.getSVar(damage).equals("Count$xPaid") || sourceName.equals("Crater's Claws")) {
|
if (sa.getSVar(damage).equals("Count$xPaid") || sourceName.equals("Crater's Claws")) {
|
||||||
@@ -131,7 +128,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
// Set PayX here to maximum value. It will be adjusted later depending on the target.
|
// Set PayX here to maximum value. It will be adjusted later depending on the target.
|
||||||
sa.setXManaCostPaid(dmg);
|
sa.setXManaCostPaid(dmg);
|
||||||
} else if (sa.getSVar(damage).contains("InYourHand") && source.isInZone(ZoneType.Hand)) {
|
} else if (sa.getSVar(damage).contains("InYourHand") && source.isInZone(ZoneType.Hand)) {
|
||||||
dmg = CardFactoryUtil.xCount(source, sa.getSVar(damage)) - 1; // the card will be spent casting the spell, so actual damage is 1 less
|
dmg = AbilityUtils.calculateAmount(source, damage, sa) - 1; // the card will be spent casting the spell, so actual damage is 1 less
|
||||||
} else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) {
|
} else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) {
|
||||||
// cards that deal damage by the number of cards in target player's hand, e.g. Sudden Impact
|
// cards that deal damage by the number of cards in target player's hand, e.g. Sudden Impact
|
||||||
if (sa.getTargetRestrictions().canTgtPlayer()) {
|
if (sa.getTargetRestrictions().canTgtPlayer()) {
|
||||||
@@ -165,6 +162,12 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
String logic = sa.getParamOrDefault("AILogic", "");
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
if ("DiscardLands".equals(logic)) {
|
if ("DiscardLands".equals(logic)) {
|
||||||
dmg = 2;
|
dmg = 2;
|
||||||
|
} else if ("OpponentHasCreatures".equals(logic)) {
|
||||||
|
for (Player opp : ai.getOpponents()) {
|
||||||
|
if (!opp.getCreaturesInPlay().isEmpty()){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (logic.startsWith("ProcRaid.")) {
|
} else if (logic.startsWith("ProcRaid.")) {
|
||||||
if (ai.getGame().getPhaseHandler().isPlayerTurn(ai) && ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
if (ai.getGame().getPhaseHandler().isPlayerTurn(ai) && ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
for (Card potentialAtkr : ai.getCreaturesInPlay()) {
|
for (Card potentialAtkr : ai.getCreaturesInPlay()) {
|
||||||
@@ -343,6 +346,9 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
List<Card> hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game);
|
List<Card> hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game);
|
||||||
|
|
||||||
|
// Filter MustTarget requirements
|
||||||
|
StaticAbilityMustTarget.filterMustTargetCards(ai, hPlay, sa);
|
||||||
|
|
||||||
CardCollection killables = CardLists.filter(hPlay, new Predicate<Card>() {
|
CardCollection killables = CardLists.filter(hPlay, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
@@ -437,63 +443,12 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
|
|
||||||
// We can hurt a planeswalker, so rank the one which is the best target
|
// We can hurt a planeswalker, so rank the one which is the best target
|
||||||
if (!hPlay.isEmpty() && pl.isOpponentOf(ai) && activator.equals(ai)) {
|
if (!hPlay.isEmpty() && pl.isOpponentOf(ai) && activator.equals(ai)) {
|
||||||
return getBestPlaneswalkerToDamage(hPlay);
|
return ComputerUtilCard.getBestPlaneswalkerToDamage(hPlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Card getBestPlaneswalkerToDamage(final List<Card> pws) {
|
|
||||||
Card bestTgt = null;
|
|
||||||
|
|
||||||
// As of right now, ranks planeswalkers by their Current Loyalty * 10 + Big buff if close to "Ultimate"
|
|
||||||
int bestScore = 0;
|
|
||||||
for (Card pw : pws) {
|
|
||||||
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
|
|
||||||
int pwScore = curLoyalty * 10;
|
|
||||||
|
|
||||||
for (SpellAbility sa : pw.getSpellAbilities()) {
|
|
||||||
if (sa.hasParam("Ultimate")) {
|
|
||||||
Integer loyaltyCost = 0;
|
|
||||||
CostRemoveCounter remLoyalty = sa.getPayCosts().getCostPartByType(CostRemoveCounter.class);
|
|
||||||
if (remLoyalty != null) {
|
|
||||||
// if remLoyalty is null, generally there's an AddCounter<0/LOYALTY> cost, like for Gideon Jura.
|
|
||||||
loyaltyCost = remLoyalty.convertAmount();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loyaltyCost != null && loyaltyCost != 0 && loyaltyCost - curLoyalty <= 1) {
|
|
||||||
// Will ultimate soon
|
|
||||||
pwScore += 10000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pwScore > bestScore) {
|
|
||||||
bestScore = pwScore;
|
|
||||||
bestTgt = pw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bestTgt;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Card getWorstPlaneswalkerToDamage(final List<Card> pws) {
|
|
||||||
Card bestTgt = null;
|
|
||||||
|
|
||||||
int bestScore = Integer.MAX_VALUE;
|
|
||||||
for (Card pw : pws) {
|
|
||||||
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
|
|
||||||
|
|
||||||
if (curLoyalty < bestScore) {
|
|
||||||
bestScore = curLoyalty;
|
|
||||||
bestTgt = pw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bestTgt;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private List<Card> getTargetableCards(Player ai, SpellAbility sa, Player pl, TargetRestrictions tgt, Player activator, Card source, Game game) {
|
private List<Card> getTargetableCards(Player ai, SpellAbility sa, Player pl, TargetRestrictions tgt, Player activator, Card source, Game game) {
|
||||||
List<Card> hPlay = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), activator, source, sa);
|
List<Card> hPlay = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), activator, source, sa);
|
||||||
|
|
||||||
@@ -582,7 +537,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
&& "P1P1".equals(sa.getParent().getParam("CounterType"))) {
|
&& "P1P1".equals(sa.getParent().getParam("CounterType"))) {
|
||||||
// assuming the SA parent is of PutCounter type. Perhaps it's possible to predict counter multipliers here somehow?
|
// 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 String amountStr = sa.getParent().getParamOrDefault("CounterNum", "1");
|
||||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
final int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
dmg += amount;
|
dmg += amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -730,12 +685,10 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// When giving priority to targeting Creatures for mandatory
|
// When giving priority to targeting Creatures for mandatory
|
||||||
// triggers
|
// triggers feel free to add the Human after we run out of good targets
|
||||||
// feel free to add the Human after we run out of good targets
|
|
||||||
|
|
||||||
// TODO: add check here if card is about to die from something
|
// TODO: add check here if card is about to die from something
|
||||||
// on the stack
|
// on the stack or from taking combat damage
|
||||||
// or from taking combat damage
|
|
||||||
|
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
boolean freePing = immediately || abCost == null
|
boolean freePing = immediately || abCost == null
|
||||||
@@ -792,8 +745,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: Improve Damage, we shouldn't just target the player just
|
// TODO: Improve Damage, we shouldn't just target the player just because we can
|
||||||
// because we can
|
|
||||||
if (sa.canTarget(enemy) && tcs.size() < tgt.getMaxTargets(source, sa)) {
|
if (sa.canTarget(enemy) && tcs.size() < tgt.getMaxTargets(source, sa)) {
|
||||||
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
||||||
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|
||||||
@@ -860,18 +812,20 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
} else if (o instanceof Player) {
|
} else if (o instanceof Player) {
|
||||||
final Player p = (Player) o;
|
final Player p = (Player) o;
|
||||||
final int restDamage = ComputerUtilCombat.predictDamageTo(p, dmg, saMe.getHostCard(), false);
|
final int restDamage = ComputerUtilCombat.predictDamageTo(p, dmg, saMe.getHostCard(), false);
|
||||||
if (!p.isOpponentOf(ai) && p.canLoseLife() && restDamage + 3 >= p.getLife() && restDamage > 0) {
|
if (restDamage > 0 && p.canLoseLife()) {
|
||||||
|
if (!p.isOpponentOf(ai) && restDamage + 3 >= p.getLife()) {
|
||||||
// from this spell will kill me
|
// from this spell will kill me
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (p.isOpponentOf(ai) && p.canLoseLife()) {
|
if (p.isOpponentOf(ai)) {
|
||||||
positive = true;
|
positive = true;
|
||||||
if (p.getLife() + 3 <= restDamage) {
|
if (p.getLife() - 3 <= restDamage) {
|
||||||
urgent = true;
|
urgent = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!positive && !(saMe instanceof AbilitySub)) {
|
if (!positive && !(saMe instanceof AbilitySub)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -898,7 +852,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
// 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.isDividedAsYouChoose();
|
final boolean divided = sa.isDividedAsYouChoose();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
PlayerCollection opps = ai.getOpponents();
|
||||||
|
|
||||||
while (sa.canAddMoreTarget()) {
|
while (sa.canAddMoreTarget()) {
|
||||||
if (tgt.canTgtPlaneswalker()) {
|
if (tgt.canTgtPlaneswalker()) {
|
||||||
@@ -926,6 +880,9 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!opps.isEmpty()) {
|
||||||
|
Player opp = opps.getFirst();
|
||||||
|
opps.remove(opp);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
if (sa.getTargets().add(opp)) {
|
if (sa.getTargets().add(opp)) {
|
||||||
if (divided) {
|
if (divided) {
|
||||||
@@ -935,6 +892,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// See if there's an indestructible target that can be used
|
// See if there's an indestructible target that can be used
|
||||||
CardCollection indestructible = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
|
CardCollection indestructible = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
|
||||||
@@ -951,7 +909,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
else if (tgt.canTgtPlaneswalker()) {
|
else if (tgt.canTgtPlaneswalker()) {
|
||||||
// Second pass for planeswalkers: choose AI's worst planeswalker
|
// Second pass for planeswalkers: choose AI's worst planeswalker
|
||||||
final Card c = getWorstPlaneswalkerToDamage(CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Predicates.and(CardPredicates.Presets.PLANESWALKERS), CardPredicates.isTargetableBy(sa)));
|
final Card c = ComputerUtilCard.getWorstPlaneswalkerToDamage(CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Predicates.and(CardPredicates.Presets.PLANESWALKERS), CardPredicates.isTargetableBy(sa)));
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
@@ -981,7 +939,6 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String damage = sa.getParam("NumDmg");
|
final String damage = sa.getParam("NumDmg");
|
||||||
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
|
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
|
||||||
@@ -1038,7 +995,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
saTgt = saTgt.getParent();
|
saTgt = saTgt.getParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
Player opponent = ai.getOpponents().min(PlayerPredicates.compareByLife());
|
Player opponent = ai.getWeakestOpponent();
|
||||||
|
|
||||||
// TODO: somehow account for the possible cost reduction?
|
// TODO: somehow account for the possible cost reduction?
|
||||||
int dmg = ComputerUtilMana.determineLeftoverMana(sa, ai, saTgt.getParam("XColor"));
|
int dmg = ComputerUtilMana.determineLeftoverMana(sa, ai, saTgt.getParam("XColor"));
|
||||||
@@ -1081,7 +1038,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
saTgt.resetTargets();
|
saTgt.resetTargets();
|
||||||
saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent);
|
saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent);
|
||||||
|
|
||||||
sa.setXManaCostPaid(dmg);
|
saTgt.setXManaCostPaid(dmg);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ public class DamageEachAi extends DamageAiBase {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
return mandatory || canPlayAI(ai, sa);
|
return mandatory || canPlayAI(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
@@ -26,9 +27,6 @@ import forge.game.spellability.TargetRestrictions;
|
|||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class DebuffAi extends SpellAbilityAi {
|
public class DebuffAi extends SpellAbilityAi {
|
||||||
// *************************************************************************
|
|
||||||
// ***************************** Debuff ************************************
|
|
||||||
// *************************************************************************
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||||
@@ -140,7 +138,6 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
Card t = null;
|
Card t = null;
|
||||||
// boolean goodt = false;
|
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) {
|
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) {
|
||||||
@@ -176,19 +173,18 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
* @return a CardCollection.
|
* @return a CardCollection.
|
||||||
*/
|
*/
|
||||||
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
|
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
|
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return c.hasAnyKeyword(kws); // don't add duplicate negative
|
return c.hasAnyKeyword(kws); // don't add duplicate negative keywords
|
||||||
// keywords
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
} // getCurseCreatures()
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -216,7 +212,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
list.remove(c);
|
list.remove(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
final CardCollection pref = CardLists.filterControlledBy(list, ai.getWeakestOpponent());
|
final CardCollection pref = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||||
final CardCollection forced = CardLists.filterControlledBy(list, ai);
|
final CardCollection forced = CardLists.filterControlledBy(list, ai);
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
@@ -242,8 +238,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - if forced targeting, just pick something without the given
|
// TODO - if forced targeting, just pick something without the given keyword
|
||||||
// keyword
|
|
||||||
Card c;
|
Card c;
|
||||||
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
||||||
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
||||||
|
|||||||
@@ -2,15 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
|
||||||
import forge.ai.AiController;
|
import forge.ai.*;
|
||||||
import forge.ai.AiProps;
|
|
||||||
import forge.ai.ComputerUtil;
|
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.ComputerUtilCost;
|
|
||||||
import forge.ai.PlayerControllerAi;
|
|
||||||
import forge.ai.SpecialAiLogic;
|
|
||||||
import forge.ai.SpecialCardAi;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -26,12 +18,13 @@ import forge.game.phase.PhaseHandler;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class DestroyAi extends SpellAbilityAi {
|
public class DestroyAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
return canPlayAI(ai, sa);
|
return checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -122,17 +115,21 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
CardCollection list;
|
CardCollection list;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Targeting
|
// Targeting
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
|
// If there's X in payment costs and it's tied to targeting, make sure we set the XManaCostPaid first
|
||||||
|
// (e.g. Heliod's Intervention)
|
||||||
|
if ("X".equals(sa.getTargetRestrictions().getMinTargets()) && sa.getSVar("X").equals("Count$xPaid")) {
|
||||||
|
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
|
sa.getRootAbility().setXManaCostPaid(xPay);
|
||||||
|
}
|
||||||
|
|
||||||
// Assume there where already enough targets chosen by AI Logic Above
|
// Assume there where already enough targets chosen by AI Logic Above
|
||||||
if (!sa.canAddMoreTarget() && sa.isTargetNumberValid()) {
|
if (sa.hasParam("AILogic") && !sa.canAddMoreTarget() && sa.isTargetNumberValid()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,11 +137,11 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
int maxTargets;
|
int maxTargets;
|
||||||
|
|
||||||
if (sa.costHasManaX()) {
|
if (sa.getRootAbility().costHasManaX()) {
|
||||||
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
|
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
|
||||||
maxTargets = ComputerUtilCost.getMaxXValue(sa, ai);
|
maxTargets = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
// need to set XPaid to get the right number for
|
// need to set XPaid to get the right number for
|
||||||
sa.setXManaCostPaid(maxTargets);
|
sa.getRootAbility().setXManaCostPaid(maxTargets);
|
||||||
// need to check for maxTargets
|
// need to check for maxTargets
|
||||||
maxTargets = Math.min(maxTargets, sa.getMaxTargets());
|
maxTargets = Math.min(maxTargets, sa.getMaxTargets());
|
||||||
} else {
|
} else {
|
||||||
@@ -226,6 +223,10 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
// target loop
|
// target loop
|
||||||
// TODO use can add more Targets
|
// TODO use can add more Targets
|
||||||
while (sa.getTargets().size() < maxTargets) {
|
while (sa.getTargets().size() < maxTargets) {
|
||||||
|
// filter by MustTarget requirement
|
||||||
|
CardCollection originalList = new CardCollection(list);
|
||||||
|
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
@@ -286,6 +287,12 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore original list for next loop if filtered by MustTarget requirement
|
||||||
|
if (mustTargetFiltered) {
|
||||||
|
list = originalList;
|
||||||
|
}
|
||||||
|
|
||||||
list.remove(choice);
|
list.remove(choice);
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,12 +70,12 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
return doMassRemovalLogic(ai, sa);
|
return doMassRemovalLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
|
public static boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
Player opponent = ai.getWeakestOpponent(); // TODO: how should this AI logic work for multiplayer and getOpponents()?
|
|
||||||
|
|
||||||
final int CREATURE_EVAL_THRESHOLD = 200;
|
// if we hit the whole board, the other opponents who are not the reason to cast this probably still suffer a bit too
|
||||||
|
final int CREATURE_EVAL_THRESHOLD = 200 / (!sa.usesTargeting() ? ai.getOpponents().size() : 1);
|
||||||
|
|
||||||
if (logic.equals("Always")) {
|
if (logic.equals("Always")) {
|
||||||
return true; // e.g. Tetzimoc, Primal Death, where we want to cast the permanent even if the removal trigger does nothing
|
return true; // e.g. Tetzimoc, Primal Death, where we want to cast the permanent even if the removal trigger does nothing
|
||||||
@@ -93,10 +93,10 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
valid = valid.replace("X", Integer.toString(xPay));
|
valid = valid.replace("X", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield),
|
// TODO should probably sort results when targeted to use on biggest threat instead of first match
|
||||||
valid.split(","), source.getController(), source, sa);
|
for (Player opponent: ai.getOpponents()) {
|
||||||
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||||
source.getController(), source, sa);
|
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||||
|
|
||||||
opplist = CardLists.filter(opplist, predicate);
|
opplist = CardLists.filter(opplist, predicate);
|
||||||
ailist = CardLists.filter(ailist, predicate);
|
ailist = CardLists.filter(ailist, predicate);
|
||||||
@@ -135,8 +135,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if only creatures are affected evaluate both lists and pass only if
|
// if only creatures are affected evaluate both lists and pass only if human creatures are more valuable
|
||||||
// human creatures are more valuable
|
|
||||||
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
||||||
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -167,7 +166,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
} // only lands involved
|
} // only lands involved
|
||||||
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
|
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
|
||||||
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds") && !opplist.isEmpty()) {
|
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
|
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
|
||||||
@@ -188,4 +187,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ import java.util.Map;
|
|||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
@@ -32,7 +33,7 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
|
|
||||||
@@ -44,9 +45,8 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
if (!opp.canBeTargetedBy(sa)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
sa.getTargets().add(opp);
|
|
||||||
}
|
}
|
||||||
|
sa.getTargets().add(opp);
|
||||||
libraryOwner = opp;
|
libraryOwner = opp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final SpellAbility root = sa.getRootAbility();
|
final SpellAbility root = sa.getRootAbility();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (mandatory && sa.canTarget(opp)) {
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -19,7 +20,7 @@ public class DigMultipleAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
|
|
||||||
@@ -27,9 +28,8 @@ public class DigMultipleAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
if (!opp.canBeTargetedBy(sa)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
sa.getTargets().add(opp);
|
|
||||||
}
|
}
|
||||||
|
sa.getTargets().add(opp);
|
||||||
libraryOwner = opp;
|
libraryOwner = opp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ public class DigMultipleAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (mandatory && sa.canTarget(opp)) {
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -31,10 +32,8 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
chance = 1;
|
chance = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
|
||||||
|
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
|
|
||||||
if ("DontMillSelf".equals(logic)) {
|
if ("DontMillSelf".equals(logic)) {
|
||||||
// A card that digs for specific things and puts everything revealed before it into graveyard
|
// A card that digs for specific things and puts everything revealed before it into graveyard
|
||||||
@@ -61,9 +60,8 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (!sa.canTarget(opp)) {
|
if (!sa.canTarget(opp)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
sa.getTargets().add(opp);
|
|
||||||
}
|
}
|
||||||
|
sa.getTargets().add(opp);
|
||||||
libraryOwner = opp;
|
libraryOwner = opp;
|
||||||
} else {
|
} else {
|
||||||
if (sa.hasParam("Valid")) {
|
if (sa.hasParam("Valid")) {
|
||||||
@@ -92,12 +90,12 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||||
return randomReturn;
|
return randomReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
@@ -60,11 +61,9 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
if (players.get(0) == ai) {
|
if (players.get(0) == ai) {
|
||||||
// the ai should only be using something like this if he has
|
// the ai should only be using something like this if he has
|
||||||
// few cards in hand,
|
// few cards in hand,
|
||||||
// cards like this better have a good drawback to be in the
|
// cards like this better have a good drawback to be in the AIs deck
|
||||||
// AIs deck
|
|
||||||
} else {
|
} else {
|
||||||
// defined to the human, so that's fine as long the human
|
// defined to the human, so that's fine as long the human has cards
|
||||||
// has cards
|
|
||||||
if (!humanHasHand) {
|
if (!humanHasHand) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -164,13 +163,11 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
} // discardTargetAI()
|
} // discardTargetAI()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (!discardTargetAI(ai, sa)) {
|
if (!discardTargetAI(ai, sa)) {
|
||||||
if (mandatory && sa.canTarget(opp)) {
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
@@ -212,9 +209,8 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
} // discardCheckDrawbackAI()
|
} // discardCheckDrawbackAI()
|
||||||
|
|
||||||
|
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
if ( mode == PlayerActionConfirmMode.Random ) { //
|
if ( mode == PlayerActionConfirmMode.Random ) {
|
||||||
// TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards
|
// TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
// assume we are looking to tap human's stuff
|
// assume we are looking to tap human's stuff
|
||||||
// TODO - check for things with untap abilities, and don't tap
|
// TODO - check for things with untap abilities, and don't tap those.
|
||||||
// those.
|
|
||||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
if (!defined.contains(opp)) {
|
if (!defined.contains(opp)) {
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
int numCards = 1;
|
int numCards = 1;
|
||||||
if (sa.hasParam("NumCards")) {
|
if (sa.hasParam("NumCards")) {
|
||||||
numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
|
numCards = AbilityUtils.calculateAmount(source, sa.getParam("NumCards"), sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean xPaid = false;
|
boolean xPaid = false;
|
||||||
@@ -257,7 +257,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
} else {
|
} else {
|
||||||
numCards = ComputerUtilCost.getMaxXValue(sa, ai);
|
numCards = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
// try not to overdraw
|
// try not to overdraw
|
||||||
int safeDraw = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
|
int safeDraw = Math.abs(Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3));
|
||||||
if (sa.getHostCard().isInstant() || sa.getHostCard().isSorcery()) { safeDraw++; } // card will be spent
|
if (sa.getHostCard().isInstant() || sa.getHostCard().isSorcery()) { safeDraw++; } // card will be spent
|
||||||
numCards = Math.min(numCards, safeDraw);
|
numCards = Math.min(numCards, safeDraw);
|
||||||
|
|
||||||
@@ -377,7 +377,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
// checks what the ai prevent from casting it on itself
|
// checks what the ai prevent from casting it on itself
|
||||||
// if spell is not mandatory
|
// if spell is not mandatory
|
||||||
if (aiTarget && !ai.cantLose()) {
|
if (aiTarget && !ai.cantLose()) {
|
||||||
if (numCards >= computerLibrarySize) {
|
if (numCards >= computerLibrarySize - 3) {
|
||||||
if (xPaid) {
|
if (xPaid) {
|
||||||
numCards = computerLibrarySize - 1;
|
numCards = computerLibrarySize - 1;
|
||||||
if (numCards <= 0 && !mandatory) {
|
if (numCards <= 0 && !mandatory) {
|
||||||
@@ -422,8 +422,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
root.setXManaCostPaid(numCards);
|
root.setXManaCostPaid(numCards);
|
||||||
} else {
|
} else {
|
||||||
// Don't draw too many cards and then risk discarding
|
// Don't draw too many cards and then risk discarding cards at EOT
|
||||||
// cards at EOT
|
|
||||||
if (!drawback && !mandatory) {
|
if (!drawback && !mandatory) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -441,7 +440,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// use xPaid abilties only for itself
|
// use xPaid abilities only for itself
|
||||||
if (xPaid) {
|
if (xPaid) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -493,7 +492,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
// TODO: consider if human is the defined player
|
// TODO: consider if human is the defined player
|
||||||
|
|
||||||
// ability is not targeted
|
// ability is not targeted
|
||||||
if (numCards >= computerLibrarySize) {
|
if (numCards >= computerLibrarySize - 3) {
|
||||||
if (ai.isCardInPlay("Laboratory Maniac")) {
|
if (ai.isCardInPlay("Laboratory Maniac")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -509,8 +508,7 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
&& game.getPhaseHandler().isPlayerTurn(ai)
|
&& game.getPhaseHandler().isPlayerTurn(ai)
|
||||||
&& !sa.isTrigger()
|
&& !sa.isTrigger()
|
||||||
&& !assumeSafeX) {
|
&& !assumeSafeX) {
|
||||||
// Don't draw too many cards and then risk discarding cards at
|
// Don't draw too many cards and then risk discarding cards at EOT
|
||||||
// EOT
|
|
||||||
if (!drawback) {
|
if (!drawback) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
||||||
@@ -233,10 +233,10 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
return ai.getCreaturesInPlay().size() >= i;
|
return ai.getCreaturesInPlay().size() >= i;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if (logic.equals("CastFromGraveThisTurn")) {
|
} else if (logic.equals("ReplaySpell")) {
|
||||||
CardCollection list = new CardCollection(game.getCardsIn(ZoneType.Graveyard));
|
CardCollection list = new CardCollection(game.getCardsIn(ZoneType.Graveyard));
|
||||||
list = CardLists.getValidCards(list, sa.getTargetRestrictions().getValidTgts(), ai, sa.getHostCard(), sa);
|
list = CardLists.getValidCards(list, sa.getTargetRestrictions().getValidTgts(), ai, sa.getHostCard(), sa);
|
||||||
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) {
|
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false, false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (logic.equals("Bribe")) {
|
} else if (logic.equals("Bribe")) {
|
||||||
@@ -313,6 +313,5 @@ public class EffectAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
|
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ public final class EncodeAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -69,4 +69,3 @@ public class ExploreAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import forge.game.keyword.Keyword;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
@@ -50,6 +51,9 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
||||||
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
||||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||||
|
// Filter MustTarget requirements
|
||||||
|
StaticAbilityMustTarget.filterMustTargetCards(ai, humCreatures, sa);
|
||||||
|
|
||||||
if (humCreatures.isEmpty())
|
if (humCreatures.isEmpty())
|
||||||
return false; //prevent IndexOutOfBoundsException on MOJHOSTO variant
|
return false; //prevent IndexOutOfBoundsException on MOJHOSTO variant
|
||||||
|
|
||||||
@@ -63,8 +67,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
for (Card humanCreature : humCreatures) {
|
for (Card humanCreature : humCreatures) {
|
||||||
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= fighter1.getNetPower()
|
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= fighter1.getNetPower()
|
||||||
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(fighter1)) {
|
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(fighter1)) {
|
||||||
// todo: check min/max targets; see if we picked the best
|
// todo: check min/max targets; see if we picked the best matchup
|
||||||
// matchup
|
|
||||||
sa.getTargets().add(humanCreature);
|
sa.getTargets().add(humanCreature);
|
||||||
return true;
|
return true;
|
||||||
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
|
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
|
||||||
@@ -81,8 +84,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
for (Card aiCreature : aiCreatures) {
|
for (Card aiCreature : aiCreatures) {
|
||||||
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
|
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
|
||||||
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(aiCreature)) {
|
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(aiCreature)) {
|
||||||
// todo: check min/max targets; see if we picked the
|
// todo: check min/max targets; see if we picked the best matchup
|
||||||
// best matchup
|
|
||||||
sa.getTargets().add(humanCreature);
|
sa.getTargets().add(humanCreature);
|
||||||
sa.getTargets().add(aiCreature);
|
sa.getTargets().add(aiCreature);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -14,16 +14,33 @@ public class FlipACoinAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
String AILogic = sa.getParam("AILogic");
|
String ailogic = sa.getParam("AILogic");
|
||||||
if (AILogic.equals("Never")) {
|
if (ailogic.equals("Never")) {
|
||||||
return false;
|
return false;
|
||||||
} else if (AILogic.equals("PhaseOut")) {
|
} else if (ailogic.equals("PhaseOut")) {
|
||||||
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (AILogic.equals("KillOrcs")) {
|
} else if (ailogic.equals("Bangchuckers")) {
|
||||||
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sa.resetTargets();
|
||||||
|
for (Player o : ai.getOpponents()) {
|
||||||
|
if (sa.canTarget(o) && o.canLoseLife() && !o.cantLose()) {
|
||||||
|
sa.getTargets().add(o);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Card c : ai.getOpponents().getCreaturesInPlay()) {
|
||||||
|
if (sa.canTarget(c)) {
|
||||||
|
sa.getTargets().add(c);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else if (ailogic.equals("KillOrcs")) {
|
||||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class FogAi extends SpellAbilityAi {
|
|||||||
final Card hostCard = sa.getHostCard();
|
final Card hostCard = sa.getHostCard();
|
||||||
|
|
||||||
// Don't cast it, if the effect is already in place
|
// Don't cast it, if the effect is already in place
|
||||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
if (game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ public class GameLossAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only one SA Lose the Game card right now, which is Door to
|
// Only one SA Lose the Game card right now, which is Door to Nothingness
|
||||||
// Nothingness
|
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
@@ -29,20 +28,23 @@ public class GameLossAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
Player loser = ai;
|
||||||
|
|
||||||
// Phage the Untouchable
|
// Phage the Untouchable
|
||||||
// (Final Fortune would need to attach it's delayed trigger to a
|
// (Final Fortune would need to attach it's delayed trigger to a
|
||||||
// specific turn, which can't be done yet)
|
// specific turn, which can't be done yet)
|
||||||
Player opp = ai.getWeakestOpponent();
|
if (ai.getGame().getCombat() != null) {
|
||||||
|
loser = ai.getGame().getCombat().getDefenderPlayerByAttacker(sa.getHostCard());
|
||||||
|
}
|
||||||
|
|
||||||
if (!mandatory && opp.cantLose()) {
|
if (!mandatory && loser.cantLose()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(loser);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -24,4 +24,3 @@ public class InvestigateAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
56
forge-ai/src/main/java/forge/ai/ability/LearnAi.java
Normal file
56
forge-ai/src/main/java/forge/ai/ability/LearnAi.java
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.PlayerControllerAi;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardCollection;
|
||||||
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
public class LearnAi extends SpellAbilityAi {
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
// For the time being, Learn is treated as universally positive due to being optional
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
return mandatory || canPlayAI(aiPlayer, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
|
return canPlayAI(aiPlayer, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Card chooseCardToLearn(CardCollection options, Player ai, SpellAbility sa) {
|
||||||
|
CardCollection sideboard = CardLists.filter(options, CardPredicates.inZone(ZoneType.Sideboard));
|
||||||
|
CardCollection hand = CardLists.filter(options, CardPredicates.inZone(ZoneType.Hand));
|
||||||
|
hand.remove(sa.getHostCard()); // this card will be used in the process, don't consider it for discard
|
||||||
|
|
||||||
|
CardCollection lessons = CardLists.filter(sideboard, CardPredicates.isType("Lesson"));
|
||||||
|
CardCollection goodDiscards = ((PlayerControllerAi)ai.getController()).getAi().getCardsToDiscard(1, 1, hand, sa);
|
||||||
|
|
||||||
|
if (!lessons.isEmpty()) {
|
||||||
|
return ComputerUtilCard.getBestAI(lessons);
|
||||||
|
} else if (!goodDiscards.isEmpty()) {
|
||||||
|
return ComputerUtilCard.getWorstAI(goodDiscards);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't choose anything if there's no good option
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,10 @@ import java.util.Map;
|
|||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CounterEnumType;
|
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;
|
||||||
@@ -24,11 +26,13 @@ public class LegendaryRuleAi extends SpellAbilityAi {
|
|||||||
return false; // should not get here
|
return false; // should not get here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
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);
|
CardCollection legends = new CardCollection(options);
|
||||||
|
CardCollection badOptions = ComputerUtil.choosePermanentsToSacrifice(ai, legends, legends.size() -1, sa, false, false);
|
||||||
|
legends.removeAll(badOptions);
|
||||||
|
Card firstOption = Iterables.getFirst(legends, null);
|
||||||
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
|
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
|
||||||
|
|
||||||
if (choosingFromPlanewalkers) {
|
if (choosingFromPlanewalkers) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -19,7 +20,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
final int myLife = aiPlayer.getLife();
|
final int myLife = aiPlayer.getLife();
|
||||||
Player opponent = aiPlayer.getWeakestOpponent();
|
Player opponent = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||||
final int hLife = opponent.getLife();
|
final int hLife = opponent.getLife();
|
||||||
|
|
||||||
if (!aiPlayer.canGainLife()) {
|
if (!aiPlayer.canGainLife()) {
|
||||||
@@ -73,9 +74,8 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
||||||
final boolean mandatory) {
|
final boolean mandatory) {
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.AiProps;
|
import forge.ai.AiProps;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
@@ -154,7 +155,7 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
|
|||||||
final boolean mandatory) {
|
final boolean mandatory) {
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||||
|
|||||||
@@ -266,8 +266,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
if (!hasTgt && mandatory) {
|
if (!hasTgt && mandatory) {
|
||||||
// need to target something but its neither negative against
|
// need to target something but its neither negative against
|
||||||
// opponents,
|
// opponents, nor positive against allies
|
||||||
// nor posive against allies
|
|
||||||
|
|
||||||
// hurting ally is probably better than healing opponent
|
// hurting ally is probably better than healing opponent
|
||||||
// look for Lifegain not Negative (case of lifegain negated)
|
// look for Lifegain not Negative (case of lifegain negated)
|
||||||
@@ -295,8 +294,7 @@ public class LifeGainAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(ally);
|
sa.getTargets().add(ally);
|
||||||
hasTgt = true;
|
hasTgt = true;
|
||||||
}
|
}
|
||||||
// better heal opponent which most life then the one with the
|
// better heal opponent which most life then the one with the lowest
|
||||||
// lowest
|
|
||||||
if (!hasTgt) {
|
if (!hasTgt) {
|
||||||
Player opp = opps.max(PlayerPredicates.compareByLife());
|
Player opp = opps.max(PlayerPredicates.compareByLife());
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||||
|
|
||||||
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
@@ -60,7 +59,6 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
@@ -123,12 +121,12 @@ public class LifeLoseAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
|
||||||
|
|
||||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
|
||||||
|
// TODO: check against the amount we could obtain when multiple activations are possible
|
||||||
PlayerCollection filteredPlayer = tgtPlayers
|
PlayerCollection filteredPlayer = tgtPlayers
|
||||||
.filter(Predicates.and(PlayerPredicates.isOpponentOf(ai), PlayerPredicates.lifeLessOrEqualTo(amount)));
|
.filter(Predicates.and(PlayerPredicates.isOpponentOf(ai), PlayerPredicates.lifeLessOrEqualTo(amount)));
|
||||||
// killing opponents asap
|
// killing opponents asap
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final int myLife = ai.getLife();
|
final int myLife = ai.getLife();
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = ai.getStrongestOpponent();
|
||||||
final int hlife = opponent.getLife();
|
final int hlife = opponent.getLife();
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
|
|
||||||
@@ -36,8 +36,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle proper calculation of X values based on Cost and what
|
// TODO handle proper calculation of X values based on Cost and what would be paid
|
||||||
// would be paid
|
|
||||||
int amount;
|
int amount;
|
||||||
// we shouldn't have to worry too much about PayX for SetLife
|
// we shouldn't have to worry too much about PayX for SetLife
|
||||||
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
@@ -58,11 +57,9 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
if (tgt.canOnlyTgtOpponent()) {
|
if (tgt.canOnlyTgtOpponent()) {
|
||||||
sa.getTargets().add(opponent);
|
sa.getTargets().add(opponent);
|
||||||
// if we can only target the human, and the Human's life
|
// if we can only target the human, and the Human's life
|
||||||
// would
|
// would go up, don't play it.
|
||||||
// go up, don't play it.
|
|
||||||
// possibly add a combo here for Magister Sphinx and
|
// possibly add a combo here for Magister Sphinx and
|
||||||
// Higedetsu's
|
// Higedetsu's (sp?) Second Rite
|
||||||
// (sp?) Second Rite
|
|
||||||
if ((amount > hlife) || !opponent.canLoseLife()) {
|
if ((amount > hlife) || !opponent.canLoseLife()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -81,8 +78,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
if (sa.getParam("Defined").equals("Player")) {
|
if (sa.getParam("Defined").equals("Player")) {
|
||||||
if (amount == 0) {
|
if (amount == 0) {
|
||||||
return false;
|
return false;
|
||||||
} else if (myLife > amount) { // will decrease computer's
|
} else if (myLife > amount) { // will decrease computer's life
|
||||||
// life
|
|
||||||
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
|
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -104,7 +100,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final int myLife = ai.getLife();
|
final int myLife = ai.getLife();
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = ai.getStrongestOpponent();
|
||||||
final int hlife = opponent.getLife();
|
final int hlife = opponent.getLife();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
@@ -118,7 +114,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
sa.setXManaCostPaid(xPay);
|
sa.setXManaCostPaid(xPay);
|
||||||
amount = xPay;
|
amount = xPay;
|
||||||
} else {
|
} else {
|
||||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
// special cases when amount can't be calculated without targeting first
|
// special cases when amount can't be calculated without targeting first
|
||||||
@@ -133,8 +129,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the Target is gaining life, target self.
|
// If the Target is gaining life, target self.
|
||||||
// if the Target is modifying how much life is gained, this needs to
|
// if the Target is modifying how much life is gained, this needs to be handled better
|
||||||
// be handled better
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ public class ManaEffectAi extends SpellAbilityAi {
|
|||||||
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(ai.getCardsIn(ZoneType.Hand), ai);
|
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(ai.getCardsIn(ZoneType.Hand), ai);
|
||||||
for (final SpellAbility testSa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, ai)) {
|
for (final SpellAbility testSa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, ai)) {
|
||||||
ManaCost cost = testSa.getPayCosts().getTotalMana();
|
ManaCost cost = testSa.getPayCosts().getTotalMana();
|
||||||
boolean canPayWithAvailableColors = cost.canBePaidWithAvaliable(ColorSet.fromNames(
|
boolean canPayWithAvailableColors = cost.canBePaidWithAvailable(ColorSet.fromNames(
|
||||||
ComputerUtilCost.getAvailableManaColors(ai, (List<Card>)null)).getColor());
|
ComputerUtilCost.getAvailableManaColors(ai, (List<Card>)null)).getColor());
|
||||||
|
|
||||||
if (cost.getCMC() == 0 && cost.countX() == 0) {
|
if (cost.getCMC() == 0 && cost.countX() == 0) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import forge.game.phase.PhaseHandler;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
@@ -52,7 +53,7 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
} else if ("ExileAndPlayOrDealDamage".equals(sa.getParam("AILogic"))) {
|
} else if ("ExileAndPlayOrDealDamage".equals(sa.getParam("AILogic"))) {
|
||||||
return (ph.is(PhaseType.MAIN1) || ph.is(PhaseType.MAIN2)) && ph.isPlayerTurn(ai); // Chandra, Torch of Defiance and similar
|
return (ph.is(PhaseType.MAIN1) || ph.is(PhaseType.MAIN2)) && ph.isPlayerTurn(ai); // Chandra, Torch of Defiance and similar
|
||||||
}
|
}
|
||||||
if (!sa.hasParam("Planeswalker")) { // Planeswalker abilities are only activated at sorcery speed
|
if (!sa.isPwAbility()) { // Planeswalker abilities are only activated at sorcery speed
|
||||||
if ("You".equals(sa.getParam("Defined")) && !(!SpellAbilityAi.isSorcerySpeed(sa) && ph.is(PhaseType.END_OF_TURN)
|
if ("You".equals(sa.getParam("Defined")) && !(!SpellAbilityAi.isSorcerySpeed(sa) && ph.is(PhaseType.END_OF_TURN)
|
||||||
&& ph.getNextTurn().equals(ai))) {
|
&& ph.getNextTurn().equals(ai))) {
|
||||||
return false; // only self-mill at opponent EOT
|
return false; // only self-mill at opponent EOT
|
||||||
@@ -214,13 +215,7 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get targeted or defined Player with largest library
|
// get targeted or defined Player with largest library
|
||||||
// TODO in Java 8 find better way
|
final Player m = Collections.max(list, PlayerPredicates.compareByZoneSize(ZoneType.Library));
|
||||||
final Player m = Collections.max(list, new Comparator<Player>() {
|
|
||||||
@Override
|
|
||||||
public int compare(Player arg0, Player arg1) {
|
|
||||||
return arg0.getCardsIn(ZoneType.Library).size() - arg1.getCardsIn(ZoneType.Library).size();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
int cardsToDiscard = m.getCardsIn(ZoneType.Library).size();
|
int cardsToDiscard = m.getCardsIn(ZoneType.Library).size();
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
Card attacker = null;
|
Card attacker = null;
|
||||||
if (sa.hasParam("DefinedAttacker")) {
|
if (sa.hasParam("DefinedAttacker")) {
|
||||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedAttacker"), sa);
|
final List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedAttacker"), sa);
|
||||||
if (cards.isEmpty()) {
|
if (cards.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||||
|
|
||||||
final Card card = sa.getHostCard();
|
final Card card = sa.getHostCard();
|
||||||
|
|
||||||
if (card.hasKeyword("MayFlashSac") && !ai.couldCastSorcery(sa)) {
|
if (card.hasKeyword("MayFlashSac") && !ai.couldCastSorcery(sa)) {
|
||||||
@@ -51,7 +50,6 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
|
|
||||||
final Card card = sa.getHostCard();
|
final Card card = sa.getHostCard();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
@@ -297,7 +295,7 @@ public class PermanentAi extends SpellAbilityAi {
|
|||||||
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return checkApiLogic(ai, sa);
|
return checkApiLogic(ai, sa) || mandatory;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ public class PermanentCreatureAi extends PermanentAi {
|
|||||||
*/
|
*/
|
||||||
@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) {
|
||||||
|
|
||||||
final Card card = sa.getHostCard();
|
final Card card = sa.getHostCard();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
|
|
||||||
@@ -176,7 +175,6 @@ public class PermanentCreatureAi extends PermanentAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (hasFloatMana || willDiscardNow || willDieNow) {
|
if (hasFloatMana || willDiscardNow || willDieNow) {
|
||||||
// Will lose mana in pool or about to discard a card in cleanup or about to die in combat, so use this opportunity
|
// Will lose mana in pool or about to discard a card in cleanup or about to die in combat, so use this opportunity
|
||||||
return true;
|
return true;
|
||||||
@@ -207,7 +205,6 @@ public class PermanentCreatureAi extends PermanentAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
|
||||||
|
|
||||||
if (!super.checkApiLogic(ai, sa)) {
|
if (!super.checkApiLogic(ai, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import forge.game.card.CardCollection;
|
|||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,7 +27,6 @@ public class PermanentNoncreatureAi extends PermanentAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
|
|
||||||
if (!super.checkApiLogic(ai, sa))
|
if (!super.checkApiLogic(ai, sa))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -40,10 +38,8 @@ public class PermanentNoncreatureAi extends PermanentAi {
|
|||||||
if (host.hasSVar("OblivionRing")) {
|
if (host.hasSVar("OblivionRing")) {
|
||||||
SpellAbility effectExile = AbilityFactory.getAbility(host.getSVar("TrigExile"), host);
|
SpellAbility effectExile = AbilityFactory.getAbility(host.getSVar("TrigExile"), host);
|
||||||
final ZoneType origin = ZoneType.listValueOf(effectExile.getParam("Origin")).get(0);
|
final ZoneType origin = ZoneType.listValueOf(effectExile.getParam("Origin")).get(0);
|
||||||
final TargetRestrictions tgt = effectExile.getTargetRestrictions();
|
effectExile.setActivatingPlayer(ai);
|
||||||
final CardCollection list = CardLists.getValidCards(game.getCardsIn(origin), tgt.getValidTgts(), ai, host,
|
CardCollection targets = CardLists.getTargetableCards(game.getCardsIn(origin), effectExile);
|
||||||
effectExile);
|
|
||||||
CardCollection targets = CardLists.getTargetableCards(list, sa);
|
|
||||||
if (sourceName.equals("Suspension Field")
|
if (sourceName.equals("Suspension Field")
|
||||||
|| sourceName.equals("Detention Sphere")) {
|
|| sourceName.equals("Detention Sphere")) {
|
||||||
// existing "exile until leaves" enchantments only target
|
// existing "exile until leaves" enchantments only target
|
||||||
|
|||||||
@@ -1,42 +1,27 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
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;
|
||||||
import com.google.common.collect.Lists;
|
import forge.ai.*;
|
||||||
|
|
||||||
import forge.ai.AiController;
|
|
||||||
import forge.ai.AiPlayDecision;
|
|
||||||
import forge.ai.AiProps;
|
|
||||||
import forge.ai.ComputerUtil;
|
|
||||||
import forge.ai.ComputerUtilCard;
|
|
||||||
import forge.ai.PlayerControllerAi;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
|
||||||
import forge.card.CardStateName;
|
import forge.card.CardStateName;
|
||||||
import forge.card.CardTypeView;
|
import forge.card.CardTypeView;
|
||||||
|
import forge.card.mana.ManaCost;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameType;
|
import forge.game.GameType;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.*;
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardCollectionView;
|
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.card.CardPredicates;
|
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.spellability.Spell;
|
import forge.game.spellability.*;
|
||||||
import forge.game.spellability.SpellAbility;
|
|
||||||
import forge.game.spellability.SpellAbilityPredicates;
|
|
||||||
import forge.game.spellability.SpellPermanent;
|
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class PlayAi extends SpellAbilityAi {
|
public class PlayAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -55,35 +40,10 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
return false; // prevent infinite loop
|
return false; // prevent infinite loop
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection cards = null;
|
CardCollection cards = getPlayableCards(sa, ai);
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
if (tgt != null) {
|
|
||||||
ZoneType zone = tgt.getZone().get(0);
|
|
||||||
cards = CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), ai, source, sa);
|
|
||||||
if (cards.isEmpty()) {
|
if (cards.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (!sa.hasParam("Valid")) {
|
|
||||||
cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
|
||||||
if (cards.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.hasParam("ValidSA")) {
|
|
||||||
final String valid[] = {sa.getParam("ValidSA")};
|
|
||||||
final Iterator<Card> itr = cards.iterator();
|
|
||||||
while (itr.hasNext()) {
|
|
||||||
final Card c = itr.next();
|
|
||||||
final List<SpellAbility> validSA = Lists.newArrayList(Iterables.filter(AbilityUtils.getBasicSpellsFromPlayEffect(c, ai), SpellAbilityPredicates.isValid(valid, ai , c, sa)));
|
|
||||||
if (validSA.size() == 0) {
|
|
||||||
itr.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cards.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (game.getRules().hasAppliedVariant(GameType.MoJhoSto) && source.getName().equals("Jhoira of the Ghitu Avatar")) {
|
if (game.getRules().hasAppliedVariant(GameType.MoJhoSto) && source.getName().equals("Jhoira of the Ghitu Avatar")) {
|
||||||
// Additional logic for MoJhoSto:
|
// Additional logic for MoJhoSto:
|
||||||
@@ -101,25 +61,32 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that if a ValidZone is specified, there's at least something to choose from in that zone.
|
|
||||||
CardCollectionView validOpts = new CardCollection();
|
|
||||||
if (sa.hasParam("ValidZone")) {
|
|
||||||
validOpts = AbilityUtils.filterListByType(game.getCardsIn(ZoneType.valueOf(sa.getParam("ValidZone"))),
|
|
||||||
sa.getParam("Valid"), sa);
|
|
||||||
if (validOpts.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("ReplaySpell".equals(logic)) {
|
if ("ReplaySpell".equals(logic)) {
|
||||||
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"));
|
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"), false);
|
||||||
} else if (logic.startsWith("NeedsChosenCard")) {
|
} else if (logic.startsWith("NeedsChosenCard")) {
|
||||||
int minCMC = 0;
|
int minCMC = 0;
|
||||||
if (sa.getPayCosts().getCostMana() != null) {
|
if (sa.getPayCosts().getCostMana() != null) {
|
||||||
minCMC = sa.getPayCosts().getTotalMana().getCMC();
|
minCMC = sa.getPayCosts().getTotalMana().getCMC();
|
||||||
}
|
}
|
||||||
validOpts = CardLists.filter(validOpts, CardPredicates.greaterCMC(minCMC));
|
cards = CardLists.filter(cards, CardPredicates.greaterCMC(minCMC));
|
||||||
return chooseSingleCard(ai, sa, validOpts, sa.hasParam("Optional"), null, null) != null;
|
return chooseSingleCard(ai, sa, cards, sa.hasParam("Optional"), null, null) != null;
|
||||||
|
} else if ("WithTotalCMC".equals(logic)) {
|
||||||
|
// Try to play only when there are more than three playable cards.
|
||||||
|
if (cards.size() < 3)
|
||||||
|
return false;
|
||||||
|
ManaCost mana = sa.getPayCosts().getTotalMana();
|
||||||
|
if (mana.countX() > 0) {
|
||||||
|
int amount = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
|
if (amount < ComputerUtilCard.getBestAI(cards).getCMC())
|
||||||
|
return false;
|
||||||
|
int totalCMC = 0;
|
||||||
|
for (Card c : cards) {
|
||||||
|
totalCMC += c.getCMC();
|
||||||
|
}
|
||||||
|
if (amount > totalCMC)
|
||||||
|
amount = totalCMC;
|
||||||
|
sa.setXManaCostPaid(amount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source != null && source.hasKeyword(Keyword.HIDEAWAY) && source.hasRemembered()) {
|
if (source != null && source.hasKeyword(Keyword.HIDEAWAY) && source.hasRemembered()) {
|
||||||
@@ -153,18 +120,18 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ("ReplaySpell".equals(sa.getParam("AILogic"))) {
|
||||||
|
return ComputerUtil.targetPlayableSpellCard(ai, getPlayableCards(sa, ai), sa, sa.hasParam("WithoutManaCost"), mandatory);
|
||||||
|
}
|
||||||
|
|
||||||
return checkApiLogic(ai, sa);
|
return checkApiLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
// as called from PlayEffect:173
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,12 +144,19 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
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) {
|
||||||
|
// TODO needs to be aligned for MDFC along with getAbilityToPlay so the knowledge
|
||||||
|
// of which spell was the reason for the choice can be used there
|
||||||
for (SpellAbility s : c.getBasicSpells(c.getState(CardStateName.Original))) {
|
for (SpellAbility s : c.getBasicSpells(c.getState(CardStateName.Original))) {
|
||||||
Spell spell = (Spell) s;
|
Spell spell = (Spell) s;
|
||||||
s.setActivatingPlayer(ai);
|
s.setActivatingPlayer(ai);
|
||||||
// timing restrictions still apply
|
// timing restrictions still apply
|
||||||
if (!s.getRestrictions().checkTimingRestrictions(c, s))
|
if (!s.getRestrictions().checkTimingRestrictions(c, s))
|
||||||
continue;
|
continue;
|
||||||
|
if (params != null && params.containsKey("CMCLimit")) {
|
||||||
|
Integer cmcLimit = (Integer) params.get("CMCLimit");
|
||||||
|
if (spell.getPayCosts().getTotalMana().getCMC() > cmcLimit)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
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)) {
|
||||||
@@ -202,7 +176,7 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
spell = (Spell) spell.copyWithDefinedCost(abCost);
|
spell = (Spell) spell.copyWithDefinedCost(abCost);
|
||||||
}
|
}
|
||||||
if( AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, !isOptional, true)) {
|
if (AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, !(isOptional || sa.hasParam("Optional")), true)) {
|
||||||
// Before accepting, see if the spell has a valid number of targets (it should at this point).
|
// Before accepting, see if the spell has a valid number of targets (it should at this point).
|
||||||
// Proceeding past this point if the spell is not correctly targeted will result
|
// Proceeding past this point if the spell is not correctly targeted will result
|
||||||
// in "Failed to add to stack" error and the card disappearing from the game completely.
|
// in "Failed to add to stack" error and the card disappearing from the game completely.
|
||||||
@@ -214,4 +188,36 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
});
|
});
|
||||||
return ComputerUtilCard.getBestAI(tgtCards);
|
return ComputerUtilCard.getBestAI(tgtCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static CardCollection getPlayableCards(SpellAbility sa, Player ai) {
|
||||||
|
CardCollection cards = new CardCollection();
|
||||||
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
|
if (tgt != null) {
|
||||||
|
ZoneType zone = tgt.getZone().get(0);
|
||||||
|
cards = CardLists.getValidCards(ai.getGame().getCardsIn(zone), tgt.getValidTgts(), ai, source, sa);
|
||||||
|
} else if (!sa.hasParam("Valid")) {
|
||||||
|
cards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cards != null & sa.hasParam("ValidSA")) {
|
||||||
|
final String valid[] = sa.getParam("ValidSA").split(",");
|
||||||
|
final Iterator<Card> itr = cards.iterator();
|
||||||
|
while (itr.hasNext()) {
|
||||||
|
final Card c = itr.next();
|
||||||
|
if (!Iterables.any(AbilityUtils.getBasicSpellsFromPlayEffect(c, ai), SpellAbilityPredicates.isValid(valid, ai , c, sa))) {
|
||||||
|
itr.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that if a ValidZone is specified, there's at least something to choose from in that zone.
|
||||||
|
if (sa.hasParam("ValidZone")) {
|
||||||
|
cards = new CardCollection(AbilityUtils.filterListByType(ai.getGame().getCardsIn(ZoneType.valueOf(sa.getParam("ValidZone"))),
|
||||||
|
sa.getParam("Valid"), sa));
|
||||||
|
}
|
||||||
|
return cards;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,13 +30,12 @@ public class PowerExchangeAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
List<Card> list =
|
List<Card> list =
|
||||||
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||||
// AI won't try to grab cards that are filtered out of AI decks on
|
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
||||||
// purpose
|
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa);
|
return !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa) && c.getController() != ai;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
CardLists.sortByPowerAsc(list);
|
CardLists.sortByPowerAsc(list);
|
||||||
|
|||||||
@@ -202,11 +202,10 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
CardCollection list = getProtectCreatures(ai, sa);
|
CardCollection list = getProtectCreatures(ai, sa);
|
||||||
|
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), source, sa);
|
||||||
|
|
||||||
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().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)) {
|
||||||
@@ -341,7 +340,7 @@ public class ProtectAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(c);
|
sa.getTargets().add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
if (sa.getTargets().size() < tgt.getMinTargets(source, sa)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -382,8 +382,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when this happens we need to expand AI to consider if its ok for
|
// when this happens we need to expand AI to consider if its ok for everything?
|
||||||
// everything?
|
|
||||||
for (final Card card : cards) {
|
for (final Card card : cards) {
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
if (!card.getController().isOpponentOf(ai)) {
|
if (!card.getController().isOpponentOf(ai)) {
|
||||||
@@ -400,7 +399,6 @@ public class PumpAi extends PumpAiBase {
|
|||||||
if (ComputerUtilCard.shouldPumpCard(ai, sa, card, defense, attack, keywords, false)) {
|
if (ComputerUtilCard.shouldPumpCard(ai, sa, card, defense, attack, keywords, false)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) {
|
} else if (containsUsefulKeyword(ai, keywords, card, sa, attack)) {
|
||||||
|
|
||||||
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
||||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai)
|
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, ai)
|
||||||
|| game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN, ai)) {
|
|| game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN, ai)) {
|
||||||
@@ -438,7 +436,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
&& !(sa.isCurse() && defense < 0)
|
&& !(sa.isCurse() && defense < 0)
|
||||||
&& !containsNonCombatKeyword(keywords)
|
&& !containsNonCombatKeyword(keywords)
|
||||||
&& !sa.hasParam("UntilYourNextTurn")
|
&& !"UntilYourNextTurn".equals(sa.getParam("Duration"))
|
||||||
&& !"Snapcaster".equals(sa.getParam("AILogic"))
|
&& !"Snapcaster".equals(sa.getParam("AILogic"))
|
||||||
&& !isFight) {
|
&& !isFight) {
|
||||||
return false;
|
return false;
|
||||||
@@ -489,7 +487,31 @@ public class PumpAi extends PumpAiBase {
|
|||||||
// each player sacrifices one permanent, e.g. Vaevictis, Asmadi the Dire - grab the worst for allied and
|
// each player sacrifices one permanent, e.g. Vaevictis, Asmadi the Dire - grab the worst for allied and
|
||||||
// the best for opponents
|
// the best for opponents
|
||||||
return SacrificeAi.doSacOneEachLogic(ai, sa);
|
return SacrificeAi.doSacOneEachLogic(ai, sa);
|
||||||
|
} else if (sa.getParam("AILogic").equals("Destroy")) {
|
||||||
|
List<Card> tgts = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
if (tgts.isEmpty()) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Card> alliedTgts = CardLists.filter(tgts, Predicates.or(CardPredicates.isControlledByAnyOf(ai.getAllies()), CardPredicates.isController(ai)));
|
||||||
|
List<Card> oppTgts = CardLists.filter(tgts, CardPredicates.isControlledByAnyOf(ai.getRegisteredOpponents()));
|
||||||
|
|
||||||
|
Card destroyTgt = null;
|
||||||
|
if (!oppTgts.isEmpty()) {
|
||||||
|
destroyTgt = ComputerUtilCard.getBestAI(oppTgts);
|
||||||
|
} else {
|
||||||
|
// TODO: somehow limit this so that the AI doesn't always destroy own stuff when able?
|
||||||
|
destroyTgt = ComputerUtilCard.getWorstAI(alliedTgts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destroyTgt != null) {
|
||||||
|
sa.getTargets().add(destroyTgt);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (isFight) {
|
if (isFight) {
|
||||||
return FightAi.canFightAi(ai, sa, attack, defense);
|
return FightAi.canFightAi(ai, sa, attack, defense);
|
||||||
}
|
}
|
||||||
@@ -518,8 +540,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
|
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, source, sa);
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, source, sa);
|
||||||
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().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)) {
|
||||||
@@ -575,7 +596,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("Snapcaster".equals(sa.getParam("AILogic"))) {
|
if ("Snapcaster".equals(sa.getParam("AILogic"))) {
|
||||||
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) {
|
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false, false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
@@ -43,7 +44,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean grantsUsefulExtraBlockOpts(final Player ai, final SpellAbility sa, final Card card, List<String> keywords) {
|
public boolean grantsUsefulExtraBlockOpts(final Player ai, final SpellAbility sa, final Card card, List<String> keywords) {
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
||||||
@@ -109,7 +109,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
&& (card.getNetCombatDamage() > 0)
|
&& (card.getNetCombatDamage() > 0)
|
||||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS);
|
&& !ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS);
|
||||||
} else if (keyword.endsWith("CARDNAME can't attack or block.")) {
|
} else if (keyword.endsWith("CARDNAME can't attack or block.")) {
|
||||||
if (sa.hasParam("UntilYourNextTurn")) {
|
if ("UntilYourNextTurn".equals(sa.getParam("Duration"))) {
|
||||||
return CombatUtil.canAttack(card, ai) || CombatUtil.canBlock(card, true);
|
return CombatUtil.canAttack(card, ai) || CombatUtil.canBlock(card, true);
|
||||||
}
|
}
|
||||||
if (!ph.isPlayerTurn(ai)) {
|
if (!ph.isPlayerTurn(ai)) {
|
||||||
@@ -153,14 +153,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return CombatUtil.canBlockAtLeastOne(card, attackers);
|
return CombatUtil.canBlockAtLeastOne(card, attackers);
|
||||||
} else if (keyword.endsWith("CantBlockCardUIDSource")) { // can't block CARDNAME this turn
|
|
||||||
if (!ph.isPlayerTurn(ai) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
|
||||||
|| ph.getPhase().isBefore(PhaseType.MAIN1) || !CombatUtil.canBlock(sa.getHostCard(), card)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// target needs to be a creature, controlled by the player which is attacked
|
|
||||||
return !sa.getHostCard().isTapped() || (combat != null && combat.isAttacking(sa.getHostCard())
|
|
||||||
&& card.getController().equals(combat.getDefenderPlayerByAttacker(sa.getHostCard())));
|
|
||||||
} else if (keyword.endsWith("This card doesn't untap during your next untap step.")) {
|
} else if (keyword.endsWith("This card doesn't untap during your next untap step.")) {
|
||||||
return !ph.getPhase().isBefore(PhaseType.MAIN2) && !card.isUntapped() && ph.isPlayerTurn(ai)
|
return !ph.getPhase().isBefore(PhaseType.MAIN2) && !card.isUntapped() && ph.isPlayerTurn(ai)
|
||||||
&& Untap.canUntap(card);
|
&& Untap.canUntap(card);
|
||||||
@@ -201,7 +193,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final Combat combat = game.getCombat();
|
final Combat combat = game.getCombat();
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
final int newPower = card.getNetCombatDamage() + attack;
|
final int newPower = card.getNetCombatDamage() + attack;
|
||||||
//int defense = getNumDefense(sa);
|
//int defense = getNumDefense(sa);
|
||||||
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
|
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
|
||||||
@@ -479,7 +471,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}); // leaves all creatures that will be destroyed
|
}); // leaves all creatures that will be destroyed
|
||||||
} // -X/-X end
|
} // -X/-X end
|
||||||
else if (attack < 0 && !game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
else if (attack < 0 && !game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||||
// spells that give -X/0
|
// spells that give -X/0
|
||||||
boolean isMyTurn = game.getPhaseHandler().isPlayerTurn(ai);
|
boolean isMyTurn = game.getPhaseHandler().isPlayerTurn(ai);
|
||||||
if (isMyTurn) {
|
if (isMyTurn) {
|
||||||
@@ -513,7 +505,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
else {
|
else {
|
||||||
final boolean addsKeywords = !keywords.isEmpty();
|
final boolean addsKeywords = !keywords.isEmpty();
|
||||||
if (addsKeywords) {
|
if (addsKeywords) {
|
||||||
|
|
||||||
// If the keyword can prevent a creature from attacking, see if there's some kind of viable prioritization
|
// If the keyword can prevent a creature from attacking, see if there's some kind of viable prioritization
|
||||||
if (keywords.contains("CARDNAME can't attack.") || keywords.contains("CARDNAME can't attack or block.")
|
if (keywords.contains("CARDNAME can't attack.") || keywords.contains("CARDNAME can't attack or block.")
|
||||||
|| keywords.contains("HIDDEN CARDNAME can't attack.") || keywords.contains("HIDDEN CARDNAME can't attack or block.")) {
|
|| keywords.contains("HIDDEN CARDNAME can't attack.") || keywords.contains("HIDDEN CARDNAME can't attack or block.")) {
|
||||||
@@ -539,8 +530,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
|
|
||||||
protected boolean containsNonCombatKeyword(final List<String> keywords) {
|
protected boolean containsNonCombatKeyword(final List<String> keywords) {
|
||||||
for (final String keyword : keywords) {
|
for (final String keyword : keywords) {
|
||||||
// since most keywords are combat relevant check for those that are
|
// since most keywords are combat relevant check for those that are not
|
||||||
// not
|
|
||||||
if (keyword.endsWith("This card doesn't untap during your next untap step.")
|
if (keyword.endsWith("This card doesn't untap during your next untap step.")
|
||||||
|| keyword.endsWith("Shroud") || keyword.endsWith("Hexproof")) {
|
|| keyword.endsWith("Shroud") || keyword.endsWith("Hexproof")) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -48,12 +48,6 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final int power = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumAtt"), sa);
|
|
||||||
final int defense = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumDef"), sa);
|
|
||||||
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<>();
|
|
||||||
|
|
||||||
final PhaseType phase = game.getPhaseHandler().getPhase();
|
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -64,31 +58,34 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("ValidCards")) {
|
|
||||||
valid = sa.getParam("ValidCards");
|
|
||||||
}
|
|
||||||
|
|
||||||
final Player opp = ai.getWeakestOpponent();
|
|
||||||
CardCollection comp = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
|
||||||
CardCollection human = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) {
|
final Player opp = ai.getStrongestOpponent();
|
||||||
|
|
||||||
|
if (tgt != null && sa.canTarget(opp) && sa.isCurse()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tgt != null && sa.canTarget(ai) && !sa.hasParam("IsCurse")) {
|
if (tgt != null && sa.canTarget(ai) && !sa.isCurse()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!game.getStack().isEmpty() && !sa.isCurse()) {
|
final int power = AbilityUtils.calculateAmount(source, sa.getParam("NumAtt"), sa);
|
||||||
return pumpAgainstRemoval(ai, sa, comp);
|
final int defense = AbilityUtils.calculateAmount(source, sa.getParam("NumDef"), sa);
|
||||||
|
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<>();
|
||||||
|
final PhaseType phase = game.getPhaseHandler().getPhase();
|
||||||
|
|
||||||
|
if (sa.hasParam("ValidCards")) {
|
||||||
|
valid = sa.getParam("ValidCards");
|
||||||
}
|
}
|
||||||
if (sa.hasParam("IsCurse")) {
|
|
||||||
|
CardCollection comp = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
||||||
|
CardCollection human = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
||||||
|
|
||||||
|
if (sa.isCurse()) {
|
||||||
if (defense < 0) { // try to destroy creatures
|
if (defense < 0) { // try to destroy creatures
|
||||||
comp = CardLists.filter(comp, new Predicate<Card>() {
|
comp = CardLists.filter(comp, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -113,7 +110,7 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
if (phase.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
if (phase.isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||||
|| phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
|| phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())
|
|| game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())
|
||||||
|| game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
|| game.getReplacementHandler().isPreventCombatDamageThisTurn()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int totalPower = 0;
|
int totalPower = 0;
|
||||||
@@ -139,11 +136,14 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluate both lists and pass only if human creatures are more
|
// evaluate both lists and pass only if human creatures are more valuable
|
||||||
// valuable
|
|
||||||
return (ComputerUtilCard.evaluateCreatureList(comp) + 200) < ComputerUtilCard.evaluateCreatureList(human);
|
return (ComputerUtilCard.evaluateCreatureList(comp) + 200) < ComputerUtilCard.evaluateCreatureList(human);
|
||||||
} // end Curse
|
} // end Curse
|
||||||
|
|
||||||
|
if (!game.getStack().isEmpty()) {
|
||||||
|
return pumpAgainstRemoval(ai, sa, comp);
|
||||||
|
}
|
||||||
|
|
||||||
return !CardLists.getValidCards(getPumpCreatures(ai, sa, defense, power, keywords, false), valid, source.getController(), source, sa).isEmpty();
|
return !CardLists.getValidCards(getPumpCreatures(ai, sa, defense, power, keywords, false), valid, source.getController(), source, sa).isEmpty();
|
||||||
} // pumpAllCanPlayAI()
|
} // pumpAllCanPlayAI()
|
||||||
|
|
||||||
@@ -152,6 +152,17 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
// it might help so take it
|
||||||
|
if (!sa.usesTargeting() && !sa.isCurse() && sa.getParam("ValidCards") != null && sa.getParam("ValidCards").contains("YouCtrl")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// important to call canPlay first so targets are added if needed
|
||||||
|
return canPlayAI(ai, sa) || mandatory;
|
||||||
|
}
|
||||||
|
|
||||||
boolean pumpAgainstRemoval(Player ai, SpellAbility sa, List<Card> comp) {
|
boolean pumpAgainstRemoval(Player ai, SpellAbility sa, List<Card> comp) {
|
||||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
||||||
for (final Card c : comp) {
|
for (final Card c : comp) {
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ public class RearrangeTopOfLibraryAi extends SpellAbilityAi {
|
|||||||
uncastableCMCThreshold = aic.getIntProperty(AiProps.SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF);
|
uncastableCMCThreshold = aic.getIntProperty(AiProps.SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
Player p = pc.getFirst(); // FIXME: is this always a single target spell?
|
Player p = pc.getFirst(); // currently always a single target spell
|
||||||
Card top = p.getCardsIn(ZoneType.Library).getFirst();
|
Card top = p.getCardsIn(ZoneType.Library).getFirst();
|
||||||
int landsOTB = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
|
int landsOTB = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
|
||||||
int cmc = top.isSplitCard() ? Math.min(top.getCMC(Card.SplitCMCMode.LeftSplitCMC), top.getCMC(Card.SplitCMCMode.RightSplitCMC))
|
int cmc = top.isSplitCard() ? Math.min(top.getCMC(Card.SplitCMCMode.LeftSplitCMC), top.getCMC(Card.SplitCMCMode.RightSplitCMC))
|
||||||
|
|||||||
@@ -49,14 +49,6 @@ import forge.game.zone.ZoneType;
|
|||||||
*/
|
*/
|
||||||
public class RegenerateAi extends SpellAbilityAi {
|
public class RegenerateAi extends SpellAbilityAi {
|
||||||
|
|
||||||
// Ex: A:SP$Regenerate | Cost$W | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$Regenerate
|
|
||||||
// target creature.
|
|
||||||
// http://www.slightlymagic.net/wiki/Forge_AbilityFactory#Regenerate
|
|
||||||
|
|
||||||
// **************************************************************
|
|
||||||
// ********************* Regenerate ****************************
|
|
||||||
// **************************************************************
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
@@ -65,8 +57,7 @@ public class RegenerateAi extends SpellAbilityAi {
|
|||||||
boolean chance = false;
|
boolean chance = false;
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
// As far as I can tell these Defined Cards will only have one of
|
// As far as I can tell these Defined Cards will only have one of them
|
||||||
// them
|
|
||||||
final List<Card> list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("Defined"), sa);
|
final List<Card> list = AbilityUtils.getDefinedCards(hostCard, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
@@ -105,8 +96,7 @@ public class RegenerateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!game.getStack().isEmpty()) {
|
if (!game.getStack().isEmpty()) {
|
||||||
// check stack for something on the stack will kill anything i
|
// check stack for something on the stack will kill anything i control
|
||||||
// control
|
|
||||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
|
||||||
|
|
||||||
final List<Card> threatenedTargets = new ArrayList<>();
|
final List<Card> threatenedTargets = new ArrayList<>();
|
||||||
@@ -191,8 +181,7 @@ public class RegenerateAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO see if something on the stack is about to kill something i
|
// TODO see if something on the stack is about to kill something i can target
|
||||||
// can target
|
|
||||||
|
|
||||||
// choose my best X without regen
|
// choose my best X without regen
|
||||||
if (CardLists.getNotType(compTargetables, "Creature").isEmpty()) {
|
if (CardLists.getNotType(compTargetables, "Creature").isEmpty()) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.AiController;
|
import forge.ai.AiController;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.PlayerControllerAi;
|
import forge.ai.PlayerControllerAi;
|
||||||
@@ -14,10 +15,10 @@ public class RepeatAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
if (!sa.canTarget(opp)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
@@ -44,9 +45,8 @@ public class RepeatAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import forge.game.card.CardPredicates.Presets;
|
|||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilitySub;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
@@ -51,7 +50,7 @@ public class RepeatEachAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if ("OpponentHasCreatures".equals(logic)) {
|
} else if ("OpponentHasCreatures".equals(logic)) { //TODO convert this to NeedsToPlayVar
|
||||||
for (Player opp : aiPlayer.getOpponents()) {
|
for (Player opp : aiPlayer.getOpponents()) {
|
||||||
if (!opp.getCreaturesInPlay().isEmpty()){
|
if (!opp.getCreaturesInPlay().isEmpty()){
|
||||||
return true;
|
return true;
|
||||||
@@ -67,7 +66,7 @@ public class RepeatEachAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
} else if ("AllPlayerLoseLife".equals(logic)) {
|
} else if ("AllPlayerLoseLife".equals(logic)) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
AbilitySub repeat = sa.getAdditionalAbility("RepeatSubAbility");
|
SpellAbility repeat = sa.getAdditionalAbility("RepeatSubAbility");
|
||||||
|
|
||||||
String svar = repeat.getSVar(repeat.getParam("LifeAmount"));
|
String svar = repeat.getSVar(repeat.getParam("LifeAmount"));
|
||||||
// replace RememberedPlayerCtrl with YouCtrl
|
// replace RememberedPlayerCtrl with YouCtrl
|
||||||
|
|||||||
@@ -20,13 +20,9 @@ import forge.game.spellability.TargetRestrictions;
|
|||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class SacrificeAi extends SpellAbilityAi {
|
public class SacrificeAi extends SpellAbilityAi {
|
||||||
// **************************************************************
|
|
||||||
// *************************** Sacrifice ***********************
|
|
||||||
// **************************************************************
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
|
||||||
return sacrificeTgtAI(ai, sa);
|
return sacrificeTgtAI(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,8 +44,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Improve AI for triggers. If source is a creature with:
|
// Improve AI for triggers. If source is a creature with:
|
||||||
// When ETB, sacrifice a creature. Check to see if the AI has something
|
// When ETB, sacrifice a creature. Check to see if the AI has something to sacrifice
|
||||||
// to sacrifice
|
|
||||||
|
|
||||||
// Eventually, we can call the trigger of ETB abilities with not
|
// Eventually, we can call the trigger of ETB abilities with not
|
||||||
// mandatory as part of the checks to cast something
|
// mandatory as part of the checks to cast something
|
||||||
@@ -58,12 +53,11 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa) {
|
private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa) {
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final boolean destroy = sa.hasParam("Destroy");
|
final boolean destroy = sa.hasParam("Destroy");
|
||||||
|
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = ai.getStrongestOpponent();
|
||||||
|
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
@@ -109,8 +103,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount));
|
sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
final int half = (amount / 2) + (amount % 2); // Half of amount
|
final int half = (amount / 2) + (amount % 2); // Half of amount rounded up
|
||||||
// rounded up
|
|
||||||
|
|
||||||
// If the Human has at least half rounded up of the amount to be
|
// If the Human has at least half rounded up of the amount to be
|
||||||
// sacrificed, cast the spell
|
// sacrificed, cast the spell
|
||||||
@@ -130,8 +123,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
// If Sacrifice hits both players:
|
// If Sacrifice hits both players:
|
||||||
// Only cast it if Human has the full amount of valid
|
// Only cast it if Human has the full amount of valid
|
||||||
// Only cast it if AI doesn't have the full amount of Valid
|
// Only cast it if AI doesn't have the full amount of Valid
|
||||||
// TODO: Cast if the type is favorable: my "worst" valid is
|
// TODO: Cast if the type is favorable: my "worst" valid is worse than his "worst" valid
|
||||||
// worse than his "worst" valid
|
|
||||||
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
|
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
|
||||||
int amount = AbilityUtils.calculateAmount(source, num, sa);
|
int amount = AbilityUtils.calculateAmount(source, num, sa);
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,12 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.ComputerUtilMana;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
import forge.util.TextUtil;
|
|
||||||
|
|
||||||
public class SacrificeAllAi extends SpellAbilityAi {
|
public class SacrificeAllAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -22,22 +17,7 @@ public class SacrificeAllAi extends SpellAbilityAi {
|
|||||||
// based on what the expected targets could be
|
// based on what the expected targets could be
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
String valid = "";
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
if (sa.hasParam("ValidCards")) {
|
|
||||||
valid = sa.getParam("ValidCards");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valid.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
|
||||||
// Set PayX here to maximum value.
|
|
||||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
|
||||||
valid = TextUtil.fastReplace(valid, "X", Integer.toString(xPay));
|
|
||||||
}
|
|
||||||
|
|
||||||
CardCollection humanlist =
|
|
||||||
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
|
||||||
CardCollection computerlist =
|
|
||||||
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
|
||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for some costs
|
// AI currently disabled for some costs
|
||||||
@@ -46,29 +26,19 @@ public class SacrificeAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (logic.equals("HellionEruption")) {
|
||||||
|
if (ai.getCreaturesInPlay().size() < 5 || ai.getCreaturesInPlay().size() * 150 < ComputerUtilCard.evaluateCreatureList(ai.getCreaturesInPlay())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DestroyAllAi.doMassRemovalLogic(ai, sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
// prevent run-away activations - first time will always return true
|
||||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
|
|
||||||
// if only creatures are affected evaluate both lists and pass only if
|
|
||||||
// human creatures are more valuable
|
|
||||||
if ((CardLists.getNotType(humanlist, "Creature").size() == 0) && (CardLists.getNotType(computerlist, "Creature").size() == 0)) {
|
|
||||||
if ((ComputerUtilCard.evaluateCreatureList(computerlist) + 200) >= ComputerUtilCard
|
|
||||||
.evaluateCreatureList(humanlist)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} // only lands involved
|
|
||||||
else if ((CardLists.getNotType(humanlist, "Land").size() == 0) && (CardLists.getNotType(computerlist, "Land").size() == 0)) {
|
|
||||||
if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 1) >= ComputerUtilCard
|
|
||||||
.evaluatePermanentList(humanlist)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} // otherwise evaluate both lists by CMC and pass only if human
|
|
||||||
// permanents are more valuable
|
|
||||||
else if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 3) >= ComputerUtilCard
|
|
||||||
.evaluatePermanentList(humanlist)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ((MyRandom.getRandom().nextFloat() < .9667) && chance);
|
return ((MyRandom.getRandom().nextFloat() < .9667) && chance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,12 @@ package forge.ai.ability;
|
|||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtilMana;
|
||||||
|
import forge.ai.SpecialCardAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.Card.SplitCMCMode;
|
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CounterEnumType;
|
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -26,7 +24,6 @@ public class ScryAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
if (sa.usesTargeting()) { // It doesn't appear that Scry ever targets
|
if (sa.usesTargeting()) { // It doesn't appear that Scry ever targets
|
||||||
// ability is targeted
|
// ability is targeted
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
@@ -47,6 +44,11 @@ public class ScryAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||||
|
// For Brain in a Jar, avoid competing against the other ability in the opponent's EOT.
|
||||||
|
if ("BrainJar".equals(sa.getParam("AILogic"))) {
|
||||||
|
return ph.getPhase().isAfter(PhaseType.MAIN2);
|
||||||
|
}
|
||||||
|
|
||||||
// if the Scry ability requires tapping and has a mana cost, it's best done at the end of opponent's turn
|
// if the Scry ability requires tapping and has a mana cost, it's best done at the end of opponent's turn
|
||||||
// and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
|
// and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
|
||||||
// try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
|
// try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
|
||||||
@@ -66,7 +68,7 @@ public class ScryAi extends SpellAbilityAi {
|
|||||||
// in the playerturn Scry should only be done in Main1 or in upkeep if able
|
// in the playerturn Scry should only be done in Main1 or in upkeep if able
|
||||||
if (ph.isPlayerTurn(ai)) {
|
if (ph.isPlayerTurn(ai)) {
|
||||||
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||||
return ph.is(PhaseType.MAIN1) || sa.hasParam("Planeswalker");
|
return ph.is(PhaseType.MAIN1) || sa.isPwAbility();
|
||||||
} else {
|
} else {
|
||||||
return ph.is(PhaseType.UPKEEP);
|
return ph.is(PhaseType.UPKEEP);
|
||||||
}
|
}
|
||||||
@@ -102,56 +104,9 @@ public class ScryAi extends SpellAbilityAi {
|
|||||||
if ("Never".equals(aiLogic)) {
|
if ("Never".equals(aiLogic)) {
|
||||||
return false;
|
return false;
|
||||||
} else if ("BrainJar".equals(aiLogic)) {
|
} else if ("BrainJar".equals(aiLogic)) {
|
||||||
final Card source = sa.getHostCard();
|
return SpecialCardAi.BrainInAJar.consider(ai, sa);
|
||||||
|
} else if ("MultipleChoice".equals(aiLogic)) {
|
||||||
int counterNum = source.getCounters(CounterEnumType.CHARGE);
|
return SpecialCardAi.MultipleChoice.consider(ai, sa);
|
||||||
// no need for logic
|
|
||||||
if (counterNum == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int libsize = ai.getCardsIn(ZoneType.Library).size();
|
|
||||||
|
|
||||||
final CardCollection hand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.or(
|
|
||||||
CardPredicates.isType("Instant"), CardPredicates.isType("Sorcery")));
|
|
||||||
if (!hand.isEmpty()) {
|
|
||||||
// has spell that can be cast in hand with put ability
|
|
||||||
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum + 1)).isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// has spell that can be cast if one counter is removed
|
|
||||||
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum)).isEmpty()) {
|
|
||||||
sa.setXManaCostPaid(1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final CardCollection library = CardLists.filter(ai.getCardsIn(ZoneType.Library), Predicates.or(
|
|
||||||
CardPredicates.isType("Instant"), CardPredicates.isType("Sorcery")));
|
|
||||||
if (!library.isEmpty()) {
|
|
||||||
// get max cmc of instant or sorceries in the libary
|
|
||||||
int maxCMC = 0;
|
|
||||||
for (final Card c : library) {
|
|
||||||
int v = c.getCMC();
|
|
||||||
if (c.isSplitCard()) {
|
|
||||||
v = Math.max(c.getCMC(SplitCMCMode.LeftSplitCMC), c.getCMC(SplitCMCMode.RightSplitCMC));
|
|
||||||
}
|
|
||||||
if (v > maxCMC) {
|
|
||||||
maxCMC = v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// there is a spell with more CMC, no need to remove counter
|
|
||||||
if (counterNum + 1 < maxCMC) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int maxToRemove = counterNum - maxCMC + 1;
|
|
||||||
// no Scry 0, even if its catched from later stuff
|
|
||||||
if (maxToRemove <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
sa.setXManaCostPaid(maxToRemove);
|
|
||||||
} else {
|
|
||||||
// no Instant or Sorceries anymore, just scry
|
|
||||||
sa.setXManaCostPaid(Math.min(counterNum, libsize));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -163,11 +118,9 @@ public class ScryAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
double chance = .4; // 40 percent chance of milling with instant speed
|
double chance = .4; // 40 percent chance of milling with instant speed stuff
|
||||||
// stuff
|
|
||||||
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||||
chance = .667; // 66.7% chance for sorcery speed (since it will
|
chance = .667; // 66.7% chance for sorcery speed (since it will never activate EOT)
|
||||||
// never activate EOT)
|
|
||||||
}
|
}
|
||||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||||
|
|
||||||
|
|||||||
@@ -52,8 +52,7 @@ public class SetStateAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||||
// Gross generalization, but this always considers alternate
|
// Gross generalization, but this always considers alternate states more powerful
|
||||||
// states more powerful
|
|
||||||
return !sa.getHostCard().isInAlternateState();
|
return !sa.getHostCard().isInAlternateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +199,6 @@ public class SetStateAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// check which state would be better for attacking
|
// check which state would be better for attacking
|
||||||
if (ph.isPlayerTurn(ai) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
if (ph.isPlayerTurn(ai) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
boolean transformAttack = false;
|
boolean transformAttack = false;
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ public class ShuffleAi extends SpellAbilityAi {
|
|||||||
return aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN2, aiPlayer);
|
return aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN2, aiPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// not really sure when the compy would use this; maybe only after a
|
// not really sure when the compy would use this; maybe only after a human
|
||||||
// human
|
|
||||||
// deliberately put a card on top of their library
|
// deliberately put a card on top of their library
|
||||||
return false;
|
return false;
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
@@ -8,16 +9,33 @@ import forge.game.spellability.SpellAbility;
|
|||||||
public class SkipPhaseAi extends SpellAbilityAi {
|
public class SkipPhaseAi extends SpellAbilityAi {
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
return true;
|
return targetPlayer(aiPlayer, sa, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
return mandatory || canPlayAI(aiPlayer, sa);
|
return targetPlayer(aiPlayer, sa, mandatory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean targetPlayer(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
|
sa.resetTargets();
|
||||||
|
if (sa.canTarget(opp)) {
|
||||||
|
sa.getTargets().add(opp);
|
||||||
|
}
|
||||||
|
else if (mandatory && sa.canTarget(ai)) {
|
||||||
|
sa.getTargets().add(ai);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user