mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 18:58:00 +00:00
Compare commits
2678 Commits
forge-1.4.
...
forge-1.5.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d9e785aec | ||
|
|
e33be38fa6 | ||
|
|
734b6f748e | ||
|
|
104e6dc306 | ||
|
|
a336cd7b0e | ||
|
|
f62f83bdda | ||
|
|
08ec2cc9d9 | ||
|
|
4425029892 | ||
|
|
3b571cb7ec | ||
|
|
8edd870602 | ||
|
|
f81c3f40b8 | ||
|
|
0cd306d23b | ||
|
|
cafff3f379 | ||
|
|
04d336f674 | ||
|
|
4f8babf927 | ||
|
|
c2bbd84204 | ||
|
|
f87f3489d1 | ||
|
|
77329e8fe5 | ||
|
|
c947da28c2 | ||
|
|
de51f43301 | ||
|
|
bec8706fcf | ||
|
|
7b6026e17d | ||
|
|
1324b0754b | ||
|
|
7da1d97769 | ||
|
|
1b5ba15321 | ||
|
|
b299711be7 | ||
|
|
164490ffc9 | ||
|
|
eadba12739 | ||
|
|
e52e154774 | ||
|
|
878c037554 | ||
|
|
03cdddce6e | ||
|
|
f23f17be4f | ||
|
|
fa423233ab | ||
|
|
c9c15d0157 | ||
|
|
1b2e23b309 | ||
|
|
acaa61319f | ||
|
|
c476415bfc | ||
|
|
5e3786fe1c | ||
|
|
07915701c3 | ||
|
|
b69228e28b | ||
|
|
42fcd856d0 | ||
|
|
22be9fffb6 | ||
|
|
204f65faac | ||
|
|
7e2b336ecc | ||
|
|
df86422e1b | ||
|
|
89c2130ff6 | ||
|
|
9d90ca99cb | ||
|
|
f8224a0f7e | ||
|
|
eef2e004b3 | ||
|
|
4fda7abaff | ||
|
|
72aefc9152 | ||
|
|
2feac87574 | ||
|
|
adcceb74ee | ||
|
|
9d0db8580e | ||
|
|
7106098c91 | ||
|
|
51acbeeab2 | ||
|
|
77f7031f44 | ||
|
|
447f07ef7f | ||
|
|
83a1b4f35e | ||
|
|
8cbab3a5a7 | ||
|
|
8fdadd61d0 | ||
|
|
1c8b589b1f | ||
|
|
3d360d216b | ||
|
|
2194592016 | ||
|
|
7bccaa769a | ||
|
|
1982f73e3d | ||
|
|
4525633ad8 | ||
|
|
6c75215113 | ||
|
|
d841ed5858 | ||
|
|
255d508437 | ||
|
|
ee2da8d8cd | ||
|
|
c4e4bc1871 | ||
|
|
45ce34ff0b | ||
|
|
f7256824bb | ||
|
|
06f33d21ee | ||
|
|
60ae9a110b | ||
|
|
dbabf696ce | ||
|
|
bcac77bd0d | ||
|
|
030f6c3939 | ||
|
|
9fbf9903ef | ||
|
|
0b67f989b9 | ||
|
|
68e8b47838 | ||
|
|
52965b233f | ||
|
|
a239710a6b | ||
|
|
4f86ee6025 | ||
|
|
102468d2e3 | ||
|
|
ee8f8def8e | ||
|
|
4bc8a89544 | ||
|
|
e66a776f9f | ||
|
|
0961590f20 | ||
|
|
8bf4345ac3 | ||
|
|
e7f0776098 | ||
|
|
35d8ea0949 | ||
|
|
59979056b9 | ||
|
|
dbbb3741d7 | ||
|
|
7ace65b8c9 | ||
|
|
dcccae1f7c | ||
|
|
fa3a140470 | ||
|
|
d9b657acf4 | ||
|
|
b9046d8326 | ||
|
|
5b70f38a9c | ||
|
|
f94fcfc997 | ||
|
|
73e835df10 | ||
|
|
3dc4594173 | ||
|
|
1606bfa2c5 | ||
|
|
b39e0f4052 | ||
|
|
f61ac6b361 | ||
|
|
b447b4ab6b | ||
|
|
968a035672 | ||
|
|
d976bed702 | ||
|
|
720a44b4fd | ||
|
|
fb2287b8c2 | ||
|
|
6468dcc712 | ||
|
|
c5923c31e9 | ||
|
|
7ab28b5226 | ||
|
|
f862a01a78 | ||
|
|
875d019b1c | ||
|
|
a4271de319 | ||
|
|
4d00ab3ecf | ||
|
|
bebe569d10 | ||
|
|
1c147936d7 | ||
|
|
7e9220d414 | ||
|
|
01867e5352 | ||
|
|
09b638075c | ||
|
|
79a61f2e12 | ||
|
|
ed39723ab6 | ||
|
|
b22ce18572 | ||
|
|
9b55d3c36b | ||
|
|
02bb5942b3 | ||
|
|
a72655fc79 | ||
|
|
30fda0e7a8 | ||
|
|
9957c12ff8 | ||
|
|
bfee22e968 | ||
|
|
485aa06339 | ||
|
|
bd5471cb30 | ||
|
|
39c83b4870 | ||
|
|
d32858b4c4 | ||
|
|
de0348592b | ||
|
|
804a8801e5 | ||
|
|
dd34a3aa9b | ||
|
|
0f37de9627 | ||
|
|
14a0e6e3ac | ||
|
|
2f2d24eb3e | ||
|
|
06484f81f6 | ||
|
|
b3f019420f | ||
|
|
2487f7ee93 | ||
|
|
e9c4f0913d | ||
|
|
81c5912add | ||
|
|
767524fb57 | ||
|
|
40f7d164bf | ||
|
|
1ff23dcc56 | ||
|
|
a4e9aa6243 | ||
|
|
50c40311a7 | ||
|
|
d331c87ab5 | ||
|
|
c4e1552b8b | ||
|
|
94783575cc | ||
|
|
a54a0ba985 | ||
|
|
e9da0ec519 | ||
|
|
8a9a13c5d7 | ||
|
|
85d967e5a5 | ||
|
|
453bf76d63 | ||
|
|
72bb290215 | ||
|
|
6230b6f28b | ||
|
|
0f5228259a | ||
|
|
4b5bd3d619 | ||
|
|
1ad1714388 | ||
|
|
f10fd27869 | ||
|
|
1f883c993b | ||
|
|
319b470acd | ||
|
|
e09ec6c165 | ||
|
|
1fa0a2c81c | ||
|
|
a8e54c5052 | ||
|
|
f17fa767c0 | ||
|
|
c8eed79a2a | ||
|
|
537f95c79d | ||
|
|
c923a7d976 | ||
|
|
b0939aeed8 | ||
|
|
a7d359fa10 | ||
|
|
862d8ed857 | ||
|
|
83cebd7e82 | ||
|
|
1eeab52cdf | ||
|
|
686807b0d2 | ||
|
|
f0464395ab | ||
|
|
87de44dd04 | ||
|
|
5fe65ad74b | ||
|
|
58600fbfad | ||
|
|
c92be334ae | ||
|
|
8276b5c51a | ||
|
|
d7224d7515 | ||
|
|
96ff2dc09e | ||
|
|
a40da18132 | ||
|
|
8cb889da68 | ||
|
|
08d2d7f4e8 | ||
|
|
e9c273e40f | ||
|
|
9500b3d951 | ||
|
|
2b4e935373 | ||
|
|
4f9fd821ec | ||
|
|
b221b7eaab | ||
|
|
4b8ed1df67 | ||
|
|
9399097069 | ||
|
|
337f5b7320 | ||
|
|
80c70d321e | ||
|
|
676fee32c6 | ||
|
|
715ee1f9ab | ||
|
|
c5a791f361 | ||
|
|
1ec656f2ed | ||
|
|
97b579a68d | ||
|
|
59f75710dc | ||
|
|
c7e74b2ddc | ||
|
|
6380b4c8fe | ||
|
|
3d4f9e7297 | ||
|
|
bfe48d2611 | ||
|
|
3c08718b4b | ||
|
|
2f033f795f | ||
|
|
acd2ca2f23 | ||
|
|
de2731c21f | ||
|
|
5e0a8a6331 | ||
|
|
b34ca7b951 | ||
|
|
1422748447 | ||
|
|
186f7f70e4 | ||
|
|
4b1eb98e5b | ||
|
|
f7e185be3b | ||
|
|
ea6da0b7dc | ||
|
|
ae23652c0c | ||
|
|
54b86b4db2 | ||
|
|
f45779d2c3 | ||
|
|
480ebbb121 | ||
|
|
427e8c7059 | ||
|
|
86cf1b63f4 | ||
|
|
27ee03494b | ||
|
|
2f5799f453 | ||
|
|
7c419d875d | ||
|
|
d7d87b5c5d | ||
|
|
a11e7bbcb8 | ||
|
|
028e4bc196 | ||
|
|
2252b9908b | ||
|
|
02353704ca | ||
|
|
2bde0ac099 | ||
|
|
2543aabbd4 | ||
|
|
d01c22b5f9 | ||
|
|
08896bdb8e | ||
|
|
46d7491c78 | ||
|
|
9437ea5d90 | ||
|
|
184943da83 | ||
|
|
66e53ad1ed | ||
|
|
6595893e48 | ||
|
|
14ac5860f8 | ||
|
|
4554d94f32 | ||
|
|
737b2eb0f7 | ||
|
|
cb318eb8b9 | ||
|
|
557035e340 | ||
|
|
86bd570e2c | ||
|
|
d406114118 | ||
|
|
e4dd0faf5e | ||
|
|
83aa60a77c | ||
|
|
acfe00d6db | ||
|
|
d4a2ae07df | ||
|
|
926bdc1d5a | ||
|
|
ffee3af0ac | ||
|
|
42e6dffda2 | ||
|
|
c9db375d7c | ||
|
|
06de741370 | ||
|
|
a2287aff5b | ||
|
|
444aad2034 | ||
|
|
0de852043e | ||
|
|
9df1102514 | ||
|
|
8eaa535d6b | ||
|
|
b5e7de67a2 | ||
|
|
f4db8078b8 | ||
|
|
21db65e9ab | ||
|
|
1d0b60427d | ||
|
|
1760b10940 | ||
|
|
ce54805981 | ||
|
|
6df75248d8 | ||
|
|
21331d4189 | ||
|
|
c1e72c380b | ||
|
|
b38346e00c | ||
|
|
9e5cbbb9d1 | ||
|
|
f767ca34ce | ||
|
|
d4bc720dd0 | ||
|
|
1d1950d3cb | ||
|
|
038418ca0d | ||
|
|
4aa2dd4471 | ||
|
|
075a01e3ae | ||
|
|
35ea4c2a50 | ||
|
|
bc851d0b51 | ||
|
|
c11d353540 | ||
|
|
32e6da4a78 | ||
|
|
a96f9be77a | ||
|
|
04a38b79ac | ||
|
|
a1886d51f8 | ||
|
|
9cd2bc595d | ||
|
|
e592d3bc56 | ||
|
|
1da213b157 | ||
|
|
3f349d0eb3 | ||
|
|
84cac99a59 | ||
|
|
de1b198f4c | ||
|
|
5954e7053c | ||
|
|
1b4b9ad32b | ||
|
|
394e96d051 | ||
|
|
2c6151996e | ||
|
|
5c195f3cea | ||
|
|
1af6d101e8 | ||
|
|
b6b220496b | ||
|
|
ff92c4cf31 | ||
|
|
12ed04a7cc | ||
|
|
6575000c28 | ||
|
|
43f4e45827 | ||
|
|
4b6e58ddf0 | ||
|
|
0abdf7a3b5 | ||
|
|
53e0a2ad37 | ||
|
|
7e22527164 | ||
|
|
7e6928d700 | ||
|
|
4fa37ac4b9 | ||
|
|
bbe5e3c556 | ||
|
|
4c7f16baf4 | ||
|
|
cec3d26493 | ||
|
|
86edf9fd66 | ||
|
|
0adcfe55aa | ||
|
|
5a22e6a15d | ||
|
|
4d833b6f38 | ||
|
|
d05ecdf840 | ||
|
|
1f66ec432d | ||
|
|
2fcc5943fa | ||
|
|
08e909ca00 | ||
|
|
4c48e3e089 | ||
|
|
d9d32f7508 | ||
|
|
691c52fff9 | ||
|
|
c3d7a9f075 | ||
|
|
da3b2c1dc1 | ||
|
|
2ea69dde48 | ||
|
|
7c66069ee3 | ||
|
|
fd9ee6f4e1 | ||
|
|
6314550377 | ||
|
|
738fc7f52d | ||
|
|
4c129ffee4 | ||
|
|
c1750989b7 | ||
|
|
2b685348ab | ||
|
|
070fd7695a | ||
|
|
7cf1a52405 | ||
|
|
096f41172d | ||
|
|
59665029d2 | ||
|
|
3249e844f9 | ||
|
|
187c90c7df | ||
|
|
d779034da8 | ||
|
|
429d8dbf30 | ||
|
|
c5d35c9596 | ||
|
|
23265d2623 | ||
|
|
4810518d74 | ||
|
|
1e389fdef8 | ||
|
|
f31550732e | ||
|
|
29876aeb28 | ||
|
|
a90c34aac4 | ||
|
|
78456d6333 | ||
|
|
44edc80abc | ||
|
|
3f6e6f54a1 | ||
|
|
1e576cfde6 | ||
|
|
07c4ab24e7 | ||
|
|
915a6a42c2 | ||
|
|
5216b35688 | ||
|
|
3c53eec798 | ||
|
|
db080d8b10 | ||
|
|
04d129b8d8 | ||
|
|
059e7f7920 | ||
|
|
3bbfe87868 | ||
|
|
8cdc76427e | ||
|
|
11976bf964 | ||
|
|
a5f4f78018 | ||
|
|
0621f2e242 | ||
|
|
5538480862 | ||
|
|
07f126cb52 | ||
|
|
a5263b1375 | ||
|
|
6a52bdf10d | ||
|
|
ad38be34b7 | ||
|
|
a7785b0d1b | ||
|
|
26743d0ca0 | ||
|
|
fccc8f129f | ||
|
|
c1a11dd89a | ||
|
|
0730e2d774 | ||
|
|
a8bf257391 | ||
|
|
86f6a9e3f1 | ||
|
|
41f3b22237 | ||
|
|
7ad5bfb581 | ||
|
|
75e5c8d0ca | ||
|
|
1b9437ad32 | ||
|
|
d5bde7570a | ||
|
|
a0d13578f3 | ||
|
|
ed48547f36 | ||
|
|
73b97fe151 | ||
|
|
f21e1c3f8a | ||
|
|
aa230be806 | ||
|
|
26bb8e5009 | ||
|
|
5322d5a9c2 | ||
|
|
0f5eb7d5f8 | ||
|
|
9379f7283c | ||
|
|
29025cce61 | ||
|
|
7fd201d5e3 | ||
|
|
1f947d5781 | ||
|
|
8dea53bcda | ||
|
|
f5d43d0986 | ||
|
|
cf67fb456e | ||
|
|
308814bdca | ||
|
|
149a9bd7b2 | ||
|
|
e16998beb5 | ||
|
|
a9e12e38ef | ||
|
|
9c7d25f49d | ||
|
|
a52a1f5820 | ||
|
|
e0c3244667 | ||
|
|
32bf18781a | ||
|
|
5bd88d468e | ||
|
|
e758e07658 | ||
|
|
eb0ac0dde9 | ||
|
|
c81a4d50dd | ||
|
|
ae28b04615 | ||
|
|
471623cd10 | ||
|
|
4378bfdbea | ||
|
|
8ff6cbd98e | ||
|
|
f4ffe22d61 | ||
|
|
4573e30e73 | ||
|
|
832b40e235 | ||
|
|
aec0bad678 | ||
|
|
aeedde578d | ||
|
|
dfebb08e1e | ||
|
|
dbf77d8548 | ||
|
|
003c252d8a | ||
|
|
1986df1961 | ||
|
|
11c205f0c9 | ||
|
|
7d2cc8fac5 | ||
|
|
863e7733e6 | ||
|
|
e657a1b07d | ||
|
|
2f29cf7755 | ||
|
|
6d34dbc286 | ||
|
|
28f7f7baaf | ||
|
|
f3b78b1aa3 | ||
|
|
ce82a93afe | ||
|
|
fe9a35b706 | ||
|
|
febba22f45 | ||
|
|
2981668172 | ||
|
|
ed7369c98f | ||
|
|
b6409b11c7 | ||
|
|
98c51e45b4 | ||
|
|
9e6b82d10a | ||
|
|
ee49314502 | ||
|
|
29e73367a6 | ||
|
|
80aa655164 | ||
|
|
f8d7d060b9 | ||
|
|
b198e8a40e | ||
|
|
a3fb6de6b3 | ||
|
|
5252cca5c5 | ||
|
|
4cab5da076 | ||
|
|
2cdf7030f8 | ||
|
|
e30f8d0fb6 | ||
|
|
22fe17048d | ||
|
|
3d70e2e9f6 | ||
|
|
f35dc1f30d | ||
|
|
34b36a8ef2 | ||
|
|
7c105ca398 | ||
|
|
2ec4873552 | ||
|
|
aa4f3de30c | ||
|
|
e4fb939e91 | ||
|
|
68acaf77c5 | ||
|
|
308039739f | ||
|
|
7687637783 | ||
|
|
7b799eb531 | ||
|
|
9c37156906 | ||
|
|
32d6eb3ed5 | ||
|
|
faa2cef0bb | ||
|
|
7e999ea4b3 | ||
|
|
69c569c82b | ||
|
|
bcc5f2c526 | ||
|
|
35bff6a89b | ||
|
|
a26d45793e | ||
|
|
d6577d1d72 | ||
|
|
e8aeec7f87 | ||
|
|
0b75a0ee8e | ||
|
|
4163c56502 | ||
|
|
37d45ffbeb | ||
|
|
f03a1397f1 | ||
|
|
410b23974e | ||
|
|
3366843ca1 | ||
|
|
9bcc6cbc64 | ||
|
|
9636e3d2e4 | ||
|
|
140e44a3bd | ||
|
|
9f76a78b0a | ||
|
|
dd6e8d0884 | ||
|
|
66b1a795c1 | ||
|
|
8b8b9f0cf5 | ||
|
|
d5647c9e98 | ||
|
|
5d1d77f4df | ||
|
|
7a193c0618 | ||
|
|
1709ed07c7 | ||
|
|
23586a027e | ||
|
|
97c74ec2b1 | ||
|
|
961461aa19 | ||
|
|
713a9ecb3b | ||
|
|
8a46d38914 | ||
|
|
423033d2ac | ||
|
|
d01925107f | ||
|
|
47a719147c | ||
|
|
556bb2caa7 | ||
|
|
ec3813fb13 | ||
|
|
926152940e | ||
|
|
7cf186952e | ||
|
|
b2ccb5c689 | ||
|
|
3da6e688e4 | ||
|
|
567ffe84cd | ||
|
|
d97495ed41 | ||
|
|
ceab176d9f | ||
|
|
050887c53c | ||
|
|
2bc61a9e68 | ||
|
|
6b1d9356f3 | ||
|
|
1dbc34fe62 | ||
|
|
d93b041397 | ||
|
|
ecd14e1de4 | ||
|
|
923bddc90d | ||
|
|
97297f291f | ||
|
|
84850c76a9 | ||
|
|
ca8c8d35d9 | ||
|
|
18e96a1028 | ||
|
|
f27b3e469a | ||
|
|
7b47938062 | ||
|
|
d9f3bb8bdd | ||
|
|
8919532b84 | ||
|
|
481ac50726 | ||
|
|
731b3c8c36 | ||
|
|
8393bc02e4 | ||
|
|
85e5b0ff13 | ||
|
|
3b8d779b27 | ||
|
|
89dcef5b67 | ||
|
|
6ced3d38a4 | ||
|
|
7fc97562b2 | ||
|
|
80372f40b5 | ||
|
|
d9a5a27391 | ||
|
|
8c73aa93c8 | ||
|
|
59997f5cf2 | ||
|
|
3ab7db6061 | ||
|
|
3d2ba5a182 | ||
|
|
d7cb5165ef | ||
|
|
3a89700d95 | ||
|
|
f0ce722aa4 | ||
|
|
95709401a3 | ||
|
|
06f030ab73 | ||
|
|
bd0dc07c43 | ||
|
|
0d354f679c | ||
|
|
7ed37ec5f8 | ||
|
|
fa40deaa11 | ||
|
|
117d25b686 | ||
|
|
7491eb0076 | ||
|
|
1acb9f8ab1 | ||
|
|
f87b262194 | ||
|
|
6c82cabe3a | ||
|
|
7e7969988a | ||
|
|
4b51c24b66 | ||
|
|
5e0ad38ffc | ||
|
|
56557101f3 | ||
|
|
1d0cf99e48 | ||
|
|
2f5b534de6 | ||
|
|
00ae4d4824 | ||
|
|
af2a87a207 | ||
|
|
72fe5ad996 | ||
|
|
d00da28a3c | ||
|
|
81363bc537 | ||
|
|
1e98cd4939 | ||
|
|
814a8976fb | ||
|
|
86cea92ca1 | ||
|
|
f9178dcff2 | ||
|
|
fcc4b92efe | ||
|
|
44f79a5db2 | ||
|
|
034f235196 | ||
|
|
a1e16543b2 | ||
|
|
8578e2a538 | ||
|
|
092d3dac40 | ||
|
|
e9ddf8400b | ||
|
|
22b78255bb | ||
|
|
2985b48432 | ||
|
|
b249028b8e | ||
|
|
4cb2077e1a | ||
|
|
88b6223820 | ||
|
|
0b4ef6b1e3 | ||
|
|
8921ce16e6 | ||
|
|
f4606da30f | ||
|
|
41a5727a93 | ||
|
|
19cec963d2 | ||
|
|
156bc84c73 | ||
|
|
eb98eaf5af | ||
|
|
fb6d86af8a | ||
|
|
39aa6bfda1 | ||
|
|
ba59dfb01c | ||
|
|
dd044c30e6 | ||
|
|
f1fcc2c26b | ||
|
|
c0e9d82ff6 | ||
|
|
89598ab48c | ||
|
|
586de9321a | ||
|
|
ae7244a10f | ||
|
|
d95fc7352f | ||
|
|
e4b7c92c6a | ||
|
|
a52a88e0cb | ||
|
|
2ec3c08e72 | ||
|
|
b57e0a9848 | ||
|
|
eabcc78815 | ||
|
|
1b7a2730f0 | ||
|
|
caac7dc2ed | ||
|
|
d8eb3a5c7a | ||
|
|
5973d0d944 | ||
|
|
a512a7ab33 | ||
|
|
e44cada50e | ||
|
|
49e734b66d | ||
|
|
5c4efbcd04 | ||
|
|
f131aafa44 | ||
|
|
f4263e943d | ||
|
|
45cd72f7dc | ||
|
|
634289407b | ||
|
|
01ff93375a | ||
|
|
c64ef5deed | ||
|
|
a5a0c510b5 | ||
|
|
d7f8231717 | ||
|
|
f0f7cfc839 | ||
|
|
03870821c5 | ||
|
|
726084bc06 | ||
|
|
cbbc341472 | ||
|
|
664b707330 | ||
|
|
a47bba59d4 | ||
|
|
e84544afe4 | ||
|
|
cd505adc3d | ||
|
|
5b1dd964ae | ||
|
|
86ef4d0820 | ||
|
|
966e593617 | ||
|
|
eb4a355dc7 | ||
|
|
d65b645a64 | ||
|
|
f899c6e58c | ||
|
|
a554599fd7 | ||
|
|
6046860e15 | ||
|
|
1dda7d72a9 | ||
|
|
3e2f3ab1c5 | ||
|
|
286d7d043c | ||
|
|
563c089b9d | ||
|
|
ca103655bd | ||
|
|
666fb6b35c | ||
|
|
d234975cde | ||
|
|
13a6310ac0 | ||
|
|
62bfb66e11 | ||
|
|
b776929fe8 | ||
|
|
273b7286cf | ||
|
|
5bd450e0c9 | ||
|
|
777dff3c4b | ||
|
|
e3ddb00eee | ||
|
|
870ffe3437 | ||
|
|
b8ea837204 | ||
|
|
4a611fcf58 | ||
|
|
91be7b5a32 | ||
|
|
42cf0def0f | ||
|
|
10fd05478d | ||
|
|
9fbc7a17c3 | ||
|
|
3cd105bac0 | ||
|
|
559c96412a | ||
|
|
ee5187c7e7 | ||
|
|
0cb7573494 | ||
|
|
0f514be363 | ||
|
|
ca07a8103a | ||
|
|
4837bb5740 | ||
|
|
fc7cc33111 | ||
|
|
ce341302a3 | ||
|
|
4cb27c40ac | ||
|
|
84e3fc1be9 | ||
|
|
2522450930 | ||
|
|
e3e761ddc0 | ||
|
|
c95ee53137 | ||
|
|
7f9ef96178 | ||
|
|
95cbb2870c | ||
|
|
17891a62fa | ||
|
|
f1ff893962 | ||
|
|
ce6d092b16 | ||
|
|
f6577217ff | ||
|
|
283af44c48 | ||
|
|
1ea839f5d4 | ||
|
|
4a3e07bbae | ||
|
|
9ba8866b19 | ||
|
|
5dbe15e7e0 | ||
|
|
cbf38aed9c | ||
|
|
a8eb34d201 | ||
|
|
496b12913d | ||
|
|
be47a4bad3 | ||
|
|
9d28e88a24 | ||
|
|
d937a397a7 | ||
|
|
c9daed2056 | ||
|
|
804c36e8b7 | ||
|
|
7274d46865 | ||
|
|
feaec2eb6d | ||
|
|
753c0731a3 | ||
|
|
9a6b1822a9 | ||
|
|
db5e8316ec | ||
|
|
513b9db9b5 | ||
|
|
04248f0166 | ||
|
|
c60c041012 | ||
|
|
f10ec1751f | ||
|
|
561d4deb32 | ||
|
|
3e552ac655 | ||
|
|
12d8b289e5 | ||
|
|
bcf2abdbbc | ||
|
|
8747c163ee | ||
|
|
458bd8f964 | ||
|
|
19a34ba730 | ||
|
|
16294fe54a | ||
|
|
76cdfa9759 | ||
|
|
fc508b3a27 | ||
|
|
a4c2fa3797 | ||
|
|
d1575996b4 | ||
|
|
75293299ed | ||
|
|
e50f31fb5f | ||
|
|
e47cdebf14 | ||
|
|
85eaf866d9 | ||
|
|
800a9b6854 | ||
|
|
94eb94508c | ||
|
|
02a49149fa | ||
|
|
80308ab621 | ||
|
|
cb055269d3 | ||
|
|
1c6ccbb20a | ||
|
|
764310e08b | ||
|
|
673fd2dae3 | ||
|
|
5fe57fcb11 | ||
|
|
0a9617b0c4 | ||
|
|
6b06a6f274 | ||
|
|
e5c3d4595c | ||
|
|
ab0a18cc64 | ||
|
|
b534c4bbcd | ||
|
|
0ee95fae76 | ||
|
|
dcb8c54e4c | ||
|
|
802aed63df | ||
|
|
ba7cd330e6 | ||
|
|
adeec5d6a9 | ||
|
|
8b0e20aa9e | ||
|
|
a22a3dc849 | ||
|
|
f220aef52a | ||
|
|
0175cc1a26 | ||
|
|
59b6e092c9 | ||
|
|
ceb7b13a8e | ||
|
|
f68f8ba157 | ||
|
|
19a58c309e | ||
|
|
62479e1de6 | ||
|
|
04dde1c18d | ||
|
|
c7450034f5 | ||
|
|
9140dab9f1 | ||
|
|
8f4d3792c6 | ||
|
|
9d73236812 | ||
|
|
fbc0404f94 | ||
|
|
a2050ed3e6 | ||
|
|
0d2c13bf80 | ||
|
|
39061439dc | ||
|
|
d9e5d93265 | ||
|
|
b3bec906ef | ||
|
|
6d969373b9 | ||
|
|
c8ac6b9f2d | ||
|
|
45d7b43aa6 | ||
|
|
e954b40d3c | ||
|
|
07f94f9d42 | ||
|
|
18809bc918 | ||
|
|
85d32d49e2 | ||
|
|
64459b414e | ||
|
|
f0ffeefe24 | ||
|
|
df022d5df7 | ||
|
|
a2b75c2072 | ||
|
|
e7a9492200 | ||
|
|
3133dbf8b4 | ||
|
|
8fd2ffc93e | ||
|
|
39675d465d | ||
|
|
6e969ddaaa | ||
|
|
a9f4009e43 | ||
|
|
b28b5e1ec9 | ||
|
|
f4b9dd2f34 | ||
|
|
40c651cd04 | ||
|
|
b6d6da88e8 | ||
|
|
7f95e5f583 | ||
|
|
e179aa404c | ||
|
|
4235a6906e | ||
|
|
618cd6415d | ||
|
|
9d458ce8c5 | ||
|
|
311306e986 | ||
|
|
a1688a7404 | ||
|
|
da421bc5f8 | ||
|
|
db8a71cca2 | ||
|
|
c8352e4968 | ||
|
|
1d8aec2c3b | ||
|
|
459c2cfae4 | ||
|
|
32c52578e2 | ||
|
|
eccadbf6fc | ||
|
|
8d99d2032d | ||
|
|
121db288c1 | ||
|
|
b8b3fff684 | ||
|
|
33a08f67ec | ||
|
|
dffc1b2a42 | ||
|
|
55aa4350b9 | ||
|
|
40bfde20fa | ||
|
|
8ceda2b07b | ||
|
|
a592a44647 | ||
|
|
2a9b59f935 | ||
|
|
5ed0549dc4 | ||
|
|
158da7f60f | ||
|
|
58e529097f | ||
|
|
2de5beb6d1 | ||
|
|
cb5df407fd | ||
|
|
8fd0e873c7 | ||
|
|
fe9ff2f4f9 | ||
|
|
bf05d850fb | ||
|
|
266cbd5dde | ||
|
|
e222c0f7a9 | ||
|
|
12e80be6d1 | ||
|
|
5efb2c2e40 | ||
|
|
2e574caac6 | ||
|
|
74b7a40763 | ||
|
|
1ede72b64a | ||
|
|
c45fe42129 | ||
|
|
ddaffcf7a5 | ||
|
|
0d28620ee3 | ||
|
|
bbab149d3c | ||
|
|
d4e193f39a | ||
|
|
bd4dd064be | ||
|
|
2efb70e3b5 | ||
|
|
92668c3a2a | ||
|
|
3915c3ac2c | ||
|
|
51e521fbb3 | ||
|
|
a81a666b81 | ||
|
|
69a9f0fe43 | ||
|
|
9262ad4d82 | ||
|
|
b6270a71bb | ||
|
|
e31d2ae0d1 | ||
|
|
77510ac6e3 | ||
|
|
0f1e88bb25 | ||
|
|
c5a42f03e6 | ||
|
|
de1124c19e | ||
|
|
60ba4bfec6 | ||
|
|
6372242b0b | ||
|
|
9a8c4a1c8c | ||
|
|
891db98700 | ||
|
|
f7d1b73a6c | ||
|
|
7a906fdc03 | ||
|
|
e7046c9bf5 | ||
|
|
8d87b0256b | ||
|
|
802aa167a9 | ||
|
|
b83bd41716 | ||
|
|
7c8cb3053a | ||
|
|
7c9fb9f6b4 | ||
|
|
68989d3e31 | ||
|
|
51a0745348 | ||
|
|
3505a2bcd5 | ||
|
|
0581a5e42b | ||
|
|
7ea839334c | ||
|
|
be7cce4300 | ||
|
|
c78251a90e | ||
|
|
c3c8fd7186 | ||
|
|
3d97768f71 | ||
|
|
f2c93cb5dd | ||
|
|
689ef0cf8a | ||
|
|
77de4abfd1 | ||
|
|
dc16b75c94 | ||
|
|
3a0fdb4d4f | ||
|
|
32b0f8333d | ||
|
|
446dfd2257 | ||
|
|
faa9947974 | ||
|
|
c75971e8c5 | ||
|
|
2c850cbe53 | ||
|
|
a060f5f322 | ||
|
|
5367811a33 | ||
|
|
7d41ae1331 | ||
|
|
f55abfa576 | ||
|
|
79b8340b80 | ||
|
|
e0b7034f97 | ||
|
|
b79876c959 | ||
|
|
e159c25f6d | ||
|
|
d16eedd1b3 | ||
|
|
a062d61c8d | ||
|
|
471e5b5399 | ||
|
|
577f94a67f | ||
|
|
f7e05491da | ||
|
|
2771b4fe1f | ||
|
|
b5830fac6e | ||
|
|
d8511c92c0 | ||
|
|
1ea41edd6a | ||
|
|
4f79f92940 | ||
|
|
e13865a966 | ||
|
|
05e1161632 | ||
|
|
feae947742 | ||
|
|
9ef69149ee | ||
|
|
04ca668807 | ||
|
|
489af75669 | ||
|
|
c8e1aff861 | ||
|
|
373bcd0351 | ||
|
|
c08e984e35 | ||
|
|
5dee59e8e5 | ||
|
|
350dbeb12f | ||
|
|
fdc2eb66aa | ||
|
|
ef332e4568 | ||
|
|
93c7d28714 | ||
|
|
5d8225f041 | ||
|
|
b501c76f7c | ||
|
|
97526110d0 | ||
|
|
f8a4c5db92 | ||
|
|
2093d23e24 | ||
|
|
7e32b752a0 | ||
|
|
4b02708bb0 | ||
|
|
9684d69bd1 | ||
|
|
f6d07944d8 | ||
|
|
46796041c7 | ||
|
|
196a782f8c | ||
|
|
4839d05b70 | ||
|
|
d8b6140692 | ||
|
|
040e51f124 | ||
|
|
a24161db2b | ||
|
|
117501c511 | ||
|
|
1c122b99e9 | ||
|
|
6970c1e7cf | ||
|
|
d56cecdba4 | ||
|
|
8ebf514dab | ||
|
|
1376f0dde2 | ||
|
|
dbacf597da | ||
|
|
87eaf96fa1 | ||
|
|
13db59aac8 | ||
|
|
9a46768de6 | ||
|
|
3c5010cebf | ||
|
|
d25b197c09 | ||
|
|
b892cfa029 | ||
|
|
23a012364b | ||
|
|
ed0f3600f5 | ||
|
|
dd4e250432 | ||
|
|
2ffb71e053 | ||
|
|
be25cf699c | ||
|
|
1cc15fe50c | ||
|
|
a04e5c1da2 | ||
|
|
5b18ba9058 | ||
|
|
753f4d989b | ||
|
|
8960fa20c3 | ||
|
|
38344027ca | ||
|
|
90ff1abab8 | ||
|
|
7fe3799ea6 | ||
|
|
5ed6fba085 | ||
|
|
6c3573d854 | ||
|
|
d00aeffbda | ||
|
|
e247b8e368 | ||
|
|
3d54fd3b7b | ||
|
|
b19b5daf48 | ||
|
|
f48952aa94 | ||
|
|
e8d4433bb3 | ||
|
|
84b0e17572 | ||
|
|
e4ba6f56dd | ||
|
|
bdfd2e9adf | ||
|
|
849655cb2f | ||
|
|
1c391a7c7e | ||
|
|
cf1990ddf7 | ||
|
|
fad3ea497d | ||
|
|
f586525a61 | ||
|
|
832a290778 | ||
|
|
ecbba9f1c0 | ||
|
|
afeb4eb815 | ||
|
|
d0b5e78f7a | ||
|
|
1837234650 | ||
|
|
c65031ea31 | ||
|
|
14a7176325 | ||
|
|
a0c3a1eb05 | ||
|
|
f7af3865b7 | ||
|
|
51a0c08042 | ||
|
|
abc2118a77 | ||
|
|
776ac3ee2b | ||
|
|
63eaec8348 | ||
|
|
ddb8464c1c | ||
|
|
29ef7a7d3b | ||
|
|
49aa0cc459 | ||
|
|
cdf1b44cb0 | ||
|
|
61fe9f2282 | ||
|
|
0b9d254e12 | ||
|
|
77b241eb68 | ||
|
|
b98ae5844c | ||
|
|
507ce6f220 | ||
|
|
4d2a27f7e8 | ||
|
|
a90841a6b6 | ||
|
|
e9bb6e42f6 | ||
|
|
ed4b11d9ef | ||
|
|
c115f01edf | ||
|
|
8fd6bbef3b | ||
|
|
8fa68731d1 | ||
|
|
b6b586a865 | ||
|
|
457ba444f1 | ||
|
|
31a367c434 | ||
|
|
e83eb92948 | ||
|
|
5ee779846d | ||
|
|
7a3ecf8e38 | ||
|
|
9e2c96a18e | ||
|
|
6550fa28ca | ||
|
|
de9ce7ebb9 | ||
|
|
caae78b6ee | ||
|
|
495701c46c | ||
|
|
ed99ce1694 | ||
|
|
51a37e896d | ||
|
|
b431315e88 | ||
|
|
ea59f120da | ||
|
|
9bd30e8d5d | ||
|
|
39d1ef7e13 | ||
|
|
83a1b70efe | ||
|
|
01b4f803a8 | ||
|
|
5c31890066 | ||
|
|
be7f4e5192 | ||
|
|
4124c45875 | ||
|
|
06e785c75a | ||
|
|
2f384c079c | ||
|
|
e4adada542 | ||
|
|
efa86891cc | ||
|
|
bcc20bb774 | ||
|
|
937a095209 | ||
|
|
1661228168 | ||
|
|
b17b9eb4e1 | ||
|
|
2ffa1510ed | ||
|
|
d1ef107b48 | ||
|
|
aa48e9cacc | ||
|
|
3c0bfa7843 | ||
|
|
803b6331f4 | ||
|
|
fe8af8705d | ||
|
|
624365cda3 | ||
|
|
4fb59b6071 | ||
|
|
d15948e44b | ||
|
|
bf629ff518 | ||
|
|
ee9ee7f207 | ||
|
|
311d4091ff | ||
|
|
4e79f508c0 | ||
|
|
90828e6eab | ||
|
|
7d3afc35cc | ||
|
|
6f4c046732 | ||
|
|
503a8da70f | ||
|
|
1cf9a005d9 | ||
|
|
fe04d56568 | ||
|
|
93b6ac15dd | ||
|
|
500632515c | ||
|
|
598d2da9d6 | ||
|
|
05f7a9b30a | ||
|
|
0b6be04428 | ||
|
|
dcb9553807 | ||
|
|
c9c9be68b1 | ||
|
|
53e6671143 | ||
|
|
3d063d8188 | ||
|
|
8d3633fe63 | ||
|
|
ee7d64c533 | ||
|
|
5009ebe0ab | ||
|
|
9b2f3f3108 | ||
|
|
9ca9dc797e | ||
|
|
2ffc18cc89 | ||
|
|
cfeeda3c93 | ||
|
|
a2277bc2d4 | ||
|
|
694e4e7988 | ||
|
|
1c0e786489 | ||
|
|
7420e94876 | ||
|
|
73c921bb95 | ||
|
|
7a22f4f74a | ||
|
|
cdb76d344e | ||
|
|
6cbded92cb | ||
|
|
bc032a074c | ||
|
|
89a4afdc08 | ||
|
|
be6266d633 | ||
|
|
705264ba9a | ||
|
|
a0b181e6fe | ||
|
|
df50a0a38d | ||
|
|
abd182e17f | ||
|
|
312c15d824 | ||
|
|
3f0cdc9b19 | ||
|
|
5d5c42c843 | ||
|
|
1a8fe19c88 | ||
|
|
7e37495b49 | ||
|
|
3210f06931 | ||
|
|
a457ff539d | ||
|
|
afbcd90ffc | ||
|
|
8acdc6c458 | ||
|
|
4ed8f69da1 | ||
|
|
90fe657da6 | ||
|
|
91fe7b516c | ||
|
|
e3851e3f1d | ||
|
|
301014285f | ||
|
|
e467f44bf2 | ||
|
|
e8e5ac2ca7 | ||
|
|
ebb0a4b2bc | ||
|
|
d21e2666ec | ||
|
|
43140b2db3 | ||
|
|
c898e1321f | ||
|
|
4f04cbbcc8 | ||
|
|
f17e857710 | ||
|
|
2401b9b1ff | ||
|
|
e646ff1d1f | ||
|
|
9bb800ce43 | ||
|
|
6859ad2ad9 | ||
|
|
19152790b2 | ||
|
|
05751dbe1b | ||
|
|
f35f6b3edd | ||
|
|
8af7fadbf6 | ||
|
|
9190ce038f | ||
|
|
bda47748f2 | ||
|
|
96084d7b26 | ||
|
|
e96ff73b50 | ||
|
|
fcb8c4bdd6 | ||
|
|
c7c1d2d302 | ||
|
|
21ea8e377c | ||
|
|
e9409d01a6 | ||
|
|
a8c70a903f | ||
|
|
c66ca97dfc | ||
|
|
5b5517ad29 | ||
|
|
c8de886967 | ||
|
|
2ef19583a6 | ||
|
|
9e0ff5e635 | ||
|
|
427cef758f | ||
|
|
1cc1516feb | ||
|
|
daafa3f5c2 | ||
|
|
9e20af7603 | ||
|
|
306a160df0 | ||
|
|
b5686bcd2f | ||
|
|
33dd245131 | ||
|
|
25334c27f1 | ||
|
|
3f20ebb15b | ||
|
|
37849b88e2 | ||
|
|
800b3f8272 | ||
|
|
b2ecb3cb52 | ||
|
|
8a794f2e02 | ||
|
|
3c05e1674f | ||
|
|
a15b3c2a69 | ||
|
|
10c138d64e | ||
|
|
99d1718498 | ||
|
|
515985dead | ||
|
|
e30c4532b8 | ||
|
|
2ab5f19f79 | ||
|
|
f5054558c2 | ||
|
|
b726053164 | ||
|
|
cc433b6708 | ||
|
|
e95e251161 | ||
|
|
75edc751f3 | ||
|
|
b4ba84a0d1 | ||
|
|
38074a1660 | ||
|
|
ca5e20b1a9 | ||
|
|
c4db463338 | ||
|
|
34c2cb604f | ||
|
|
f058e38004 | ||
|
|
b38a8b5830 | ||
|
|
8e76829a27 | ||
|
|
f3bf092898 | ||
|
|
4dd2b87538 | ||
|
|
4d6930e903 | ||
|
|
3fecd16efd | ||
|
|
29a535c71a | ||
|
|
3fd72f35df | ||
|
|
bb3d637738 | ||
|
|
02fe620360 | ||
|
|
ebe0e350ff | ||
|
|
6dbe638a59 | ||
|
|
31b5f4181a | ||
|
|
72d0cfdbbc | ||
|
|
f934764290 | ||
|
|
e28f809a58 | ||
|
|
a88dd00a0d | ||
|
|
5d9de47fd5 | ||
|
|
7add28ad7d | ||
|
|
17b97aca28 | ||
|
|
86b422230e | ||
|
|
0193671453 | ||
|
|
c4be1b8697 | ||
|
|
52f8880627 | ||
|
|
f537e8cf78 | ||
|
|
482af12b67 | ||
|
|
2f85d50778 | ||
|
|
b51663c130 | ||
|
|
7ebc54352e | ||
|
|
6703e62823 | ||
|
|
e860e9e1af | ||
|
|
de3303abc9 | ||
|
|
a0695c08cc | ||
|
|
c5aeb0785e | ||
|
|
21b376848f | ||
|
|
f8fe7e41b4 | ||
|
|
22caa0a408 | ||
|
|
4d3945f5a4 | ||
|
|
0786012ed8 | ||
|
|
6e9a316460 | ||
|
|
fd7d0e1d99 | ||
|
|
65199c7a81 | ||
|
|
851094c523 | ||
|
|
9b31032016 | ||
|
|
a7fa173e52 | ||
|
|
de4d83372c | ||
|
|
c7f24490f3 | ||
|
|
fabc7a3a27 | ||
|
|
e830527f74 | ||
|
|
5fc5feb636 | ||
|
|
384af0001c | ||
|
|
2abc9b3f7c | ||
|
|
9242f3439c | ||
|
|
b1ba8f0aa2 | ||
|
|
20b3775280 | ||
|
|
c2bb321e17 | ||
|
|
b0160287ba | ||
|
|
284e2257f3 | ||
|
|
ec06d07f35 | ||
|
|
5eca8807d8 | ||
|
|
5b3ee63299 | ||
|
|
a6ffa3ae36 | ||
|
|
e021e66387 | ||
|
|
eb70985551 | ||
|
|
cd6dc5158e | ||
|
|
942f30557c | ||
|
|
1486d61e65 | ||
|
|
eb082d2f49 | ||
|
|
7e6e691771 | ||
|
|
8e4962ceb1 | ||
|
|
414d82bd37 | ||
|
|
4091f1d1d9 | ||
|
|
fe0f4ddf99 | ||
|
|
b0845c5795 | ||
|
|
aeddb37d9d | ||
|
|
1ff1c35486 | ||
|
|
6e585a7afa | ||
|
|
e0df16ee17 | ||
|
|
ae5cb7a17a | ||
|
|
8d79ed527f | ||
|
|
d4bc84c6bd | ||
|
|
9743a7d1b7 | ||
|
|
e5cffc623a | ||
|
|
9c0a559bce | ||
|
|
b794bc5ee0 | ||
|
|
55c71d77e1 | ||
|
|
6914e30443 | ||
|
|
1c52e8e38a | ||
|
|
c38f169d1d | ||
|
|
8f4b8dbf88 | ||
|
|
b68f28b621 | ||
|
|
a23eb2f869 | ||
|
|
8dfbf2b3b6 | ||
|
|
70b0fab66a | ||
|
|
082e2ff2cd | ||
|
|
12af50d473 | ||
|
|
b1cde02dd4 | ||
|
|
2d737ab092 | ||
|
|
8cd9220f36 | ||
|
|
838d4a98aa | ||
|
|
d32bfea94e | ||
|
|
181a7f5b9c | ||
|
|
ee00b544f7 | ||
|
|
5548c71bda | ||
|
|
ae7a6b79b7 | ||
|
|
96d2338bea | ||
|
|
21217e030b | ||
|
|
1d63f4b4a1 | ||
|
|
8843e5bfe7 | ||
|
|
48b97f8632 | ||
|
|
20184fb4c0 | ||
|
|
76000a90fb | ||
|
|
4aebd350ee | ||
|
|
13f4a0abb4 | ||
|
|
35ea297090 | ||
|
|
f11909d6da | ||
|
|
5df82652b6 | ||
|
|
8fde8ebe5f | ||
|
|
6bc20a8830 | ||
|
|
0bbd9c103a | ||
|
|
0b68e2ade6 | ||
|
|
1ca79539b6 | ||
|
|
1c2c3d6d51 | ||
|
|
bdb1243409 | ||
|
|
812d4ca519 | ||
|
|
4e7e14cbf3 | ||
|
|
2064b6517e | ||
|
|
c9f491a483 | ||
|
|
be2ec80f0c | ||
|
|
1c13e8dc10 | ||
|
|
4748aa2262 | ||
|
|
a3376b0502 | ||
|
|
c073d39cd6 | ||
|
|
b508f78f04 | ||
|
|
8424da8311 | ||
|
|
cfd9d55668 | ||
|
|
417e3bb104 | ||
|
|
320d4f62d9 | ||
|
|
6c712d86fb | ||
|
|
a0479f9610 | ||
|
|
374a744a44 | ||
|
|
81416d49de | ||
|
|
31ebd62c83 | ||
|
|
93dd4af6ed | ||
|
|
9f0308dd66 | ||
|
|
f9298703d4 | ||
|
|
3992032e8e | ||
|
|
58dacc0789 | ||
|
|
b3280923d9 | ||
|
|
e83da9ff9c | ||
|
|
8b1d0fc759 | ||
|
|
ff869933be | ||
|
|
fb0af4dc18 | ||
|
|
6f5551d78d | ||
|
|
a90efe4939 | ||
|
|
46c37b9d82 | ||
|
|
3e23eb517b | ||
|
|
9cf75ea844 | ||
|
|
5565b4a4d0 | ||
|
|
bde5967ef4 | ||
|
|
f3560238fe | ||
|
|
f97f489aba | ||
|
|
454b7715c0 | ||
|
|
ac1e279235 | ||
|
|
5950162663 | ||
|
|
6a52b46192 | ||
|
|
f5558eec7f | ||
|
|
0c39f596a3 | ||
|
|
ba3c544484 | ||
|
|
e0063727a9 | ||
|
|
7b68821a8c | ||
|
|
4a84b408d4 | ||
|
|
b9aec4ffb8 | ||
|
|
1a958e758f | ||
|
|
74527319aa | ||
|
|
a66e7ad14f | ||
|
|
e4b91416dd | ||
|
|
7735c705c0 | ||
|
|
31ab48d0d4 | ||
|
|
e5cf9da31d | ||
|
|
9eac64ab7b | ||
|
|
7131cebf7d | ||
|
|
bfb4a5ee57 | ||
|
|
21e250bf36 | ||
|
|
f5b1ac970b | ||
|
|
c85f14f27c | ||
|
|
c0cf6198a4 | ||
|
|
4d3012fee7 | ||
|
|
75726dc272 | ||
|
|
260724af80 | ||
|
|
2b37cf217f | ||
|
|
669ff579c5 | ||
|
|
d0e65fe1bc | ||
|
|
4485e46801 | ||
|
|
2265687c75 | ||
|
|
f70523bf14 | ||
|
|
48013b9339 | ||
|
|
e9e1caf9d1 | ||
|
|
5b17e2beda | ||
|
|
19bc74f88d | ||
|
|
ae0069652a | ||
|
|
5ec7f01b7a | ||
|
|
691f798f90 | ||
|
|
d5bef0861b | ||
|
|
87dd938cde | ||
|
|
c4e0d3e7ce | ||
|
|
c705722bdc | ||
|
|
6424fa151a | ||
|
|
0646e49359 | ||
|
|
6ddae9e9da | ||
|
|
efc7be1637 | ||
|
|
1de80be872 | ||
|
|
f8740f1268 | ||
|
|
d6f30c9220 | ||
|
|
f2d7004b02 | ||
|
|
6ecf800bab | ||
|
|
6f92a834da | ||
|
|
a7a85d22d5 | ||
|
|
178024e248 | ||
|
|
798e93af46 | ||
|
|
3cb125ae4a | ||
|
|
30aed49e87 | ||
|
|
d0173a2341 | ||
|
|
fb7a61ab30 | ||
|
|
13e09c73bc | ||
|
|
6fdaf4fda4 | ||
|
|
ba79089bed | ||
|
|
a8670467be | ||
|
|
4dd84cdb2e | ||
|
|
028d6c0a1f | ||
|
|
ca2584f598 | ||
|
|
17e96250f7 | ||
|
|
79fdd21817 | ||
|
|
a0daa7c4be | ||
|
|
000d0d3119 | ||
|
|
3b01b4f3dd | ||
|
|
f0613e9660 | ||
|
|
1c51ed37c2 | ||
|
|
2bfcd92da9 | ||
|
|
0bf676448b | ||
|
|
5bceff414e | ||
|
|
1b65231e30 | ||
|
|
8b39b69bb7 | ||
|
|
4dfab45544 | ||
|
|
f046dbf3cc | ||
|
|
2e5f9350eb | ||
|
|
938784d6e8 | ||
|
|
02c582a695 | ||
|
|
2851d49c3b | ||
|
|
e88af1f6ba | ||
|
|
da3b6bf19e | ||
|
|
da28e10ac7 | ||
|
|
e6b260cc60 | ||
|
|
2cea9debcf | ||
|
|
768a26a5d4 | ||
|
|
0e251ffb6a | ||
|
|
74c04ea561 | ||
|
|
e92cc2735b | ||
|
|
6a94535b78 | ||
|
|
17ad490f97 | ||
|
|
704e4c4ef7 | ||
|
|
36a301275e | ||
|
|
bb2a01512e | ||
|
|
ad73c9391d | ||
|
|
ce33d146d0 | ||
|
|
055695890e | ||
|
|
028579eed4 | ||
|
|
5c836228f7 | ||
|
|
0663eee49a | ||
|
|
4d9205699b | ||
|
|
d8a5993a28 | ||
|
|
6e7e19fd34 | ||
|
|
9e55ee9be1 | ||
|
|
ae93665645 | ||
|
|
7eb84bb6a3 | ||
|
|
3006cc9431 | ||
|
|
480629953b | ||
|
|
c54ed09d38 | ||
|
|
a098b56339 | ||
|
|
7a9f8d9788 | ||
|
|
7bd822b96a | ||
|
|
d20e9d5629 | ||
|
|
9b61a99dc3 | ||
|
|
4c1f670147 | ||
|
|
c05d299fe4 | ||
|
|
0ba8b530fe | ||
|
|
0a6f7afeff | ||
|
|
483f274bac | ||
|
|
1fd36a4623 | ||
|
|
3b1ab80c1c | ||
|
|
abe0cec346 | ||
|
|
6e1e22d6cb | ||
|
|
7650ec9323 | ||
|
|
fe3fedaf64 | ||
|
|
f21f187890 | ||
|
|
41e164d131 | ||
|
|
adb6a0b7f0 | ||
|
|
31618c1611 | ||
|
|
6eba60638b | ||
|
|
e4040d4a47 | ||
|
|
c53cd32291 | ||
|
|
172ca340ae | ||
|
|
a8451abea4 | ||
|
|
cdf67a3865 | ||
|
|
e2f2fc4804 | ||
|
|
db9a451e32 | ||
|
|
a8cf682f71 | ||
|
|
0c721f1776 | ||
|
|
f8194aac7a | ||
|
|
15dd60ff3e | ||
|
|
d54441a9b2 | ||
|
|
4e75bfb4d0 | ||
|
|
3e350d03c6 | ||
|
|
2fa44e1ae2 | ||
|
|
1e5244cee0 | ||
|
|
6043c2738d | ||
|
|
013cf5eb13 | ||
|
|
a413fe2dc1 | ||
|
|
a2bf02eb82 | ||
|
|
229485494a | ||
|
|
4ea3439667 | ||
|
|
4154d31143 | ||
|
|
30b660f802 | ||
|
|
b79cf45778 | ||
|
|
c6e9cbd69b | ||
|
|
7e493ed129 | ||
|
|
c3ed6ebe79 | ||
|
|
27e33ec1b1 | ||
|
|
7601d5a2bd | ||
|
|
a9fb42eb15 | ||
|
|
cd9cdbf1be | ||
|
|
e68a716587 | ||
|
|
bab5672669 | ||
|
|
80f083e141 | ||
|
|
91d37b38cc | ||
|
|
9349fa9217 | ||
|
|
748523f9cf | ||
|
|
e64336abe9 | ||
|
|
15450a7fba | ||
|
|
ad2f63cd8f | ||
|
|
dbd3d956fb | ||
|
|
aabb4399ed | ||
|
|
a99386ac1d | ||
|
|
b400f22d15 | ||
|
|
8843054b89 | ||
|
|
8c8bde0799 | ||
|
|
109a990b28 | ||
|
|
8e9ac2e3e7 | ||
|
|
c0ad9b1b71 | ||
|
|
01609a1f29 | ||
|
|
2f5219615d | ||
|
|
91cd93783f | ||
|
|
2d8e4e9053 | ||
|
|
9500b3a5ae | ||
|
|
84e124bbf5 | ||
|
|
0d0377ae4a | ||
|
|
9e1ec67253 | ||
|
|
4f8ee5ea52 | ||
|
|
56654ffcb1 | ||
|
|
ac48d3634b | ||
|
|
934e4cbb2c | ||
|
|
53e631d65f | ||
|
|
1483d7c969 | ||
|
|
71ddee1033 | ||
|
|
480bba7a8c | ||
|
|
9c4c529c62 | ||
|
|
2df834804e | ||
|
|
fb5fab037e | ||
|
|
fc2e170d09 | ||
|
|
77602a2aa2 | ||
|
|
dd668cb0fe | ||
|
|
2a7b1f3c68 | ||
|
|
05b4a064ab | ||
|
|
e6f64aebd1 | ||
|
|
e2d9006e5c | ||
|
|
d991861421 | ||
|
|
c04d43ac66 | ||
|
|
9cd7e8e034 | ||
|
|
ee198870f7 | ||
|
|
5138ba7605 | ||
|
|
426fab34bc | ||
|
|
72b31ac9d0 | ||
|
|
c957dab3d2 | ||
|
|
f8e522fd1e | ||
|
|
5ace15ac38 | ||
|
|
4f8ef1ef34 | ||
|
|
8c580acc42 | ||
|
|
3452039d42 | ||
|
|
20202b08f9 | ||
|
|
fdf0a30843 | ||
|
|
f807986e44 | ||
|
|
358602ad21 | ||
|
|
321805e786 | ||
|
|
141025a02c | ||
|
|
526b86ac16 | ||
|
|
0d3eb11aa3 | ||
|
|
62cf0b5a5e | ||
|
|
0b7238d93b | ||
|
|
e4e62a8267 | ||
|
|
57f95c3c5c | ||
|
|
28351a1d18 | ||
|
|
9b6e34a33f | ||
|
|
faad09f594 | ||
|
|
4b687603f8 | ||
|
|
1084d89cff | ||
|
|
586f6ce5d9 | ||
|
|
059edfe381 | ||
|
|
776f4b03ff | ||
|
|
3be5ff1e17 | ||
|
|
90d51c6dbe | ||
|
|
f792bce232 | ||
|
|
94f937ad7e | ||
|
|
0aacb10d96 | ||
|
|
07b26dd7f3 | ||
|
|
6fd8e8de9a | ||
|
|
d38898b1dd | ||
|
|
12fd77953f | ||
|
|
f1db96ddce | ||
|
|
537fd9a076 | ||
|
|
25c18e8f48 | ||
|
|
064d8a8288 | ||
|
|
8e9fecaf8f | ||
|
|
ec75e4d9d9 | ||
|
|
4ccf39517b | ||
|
|
f26c734c4d | ||
|
|
655c96361e | ||
|
|
3f4adfbc07 | ||
|
|
4d2a2b6ce2 | ||
|
|
d716493bcd | ||
|
|
919de9b0fa | ||
|
|
a97398e0b3 | ||
|
|
2cf046de5e | ||
|
|
a28e444680 | ||
|
|
03a3ef8422 | ||
|
|
f99d1ce51d | ||
|
|
bf46bd9d96 | ||
|
|
5620552344 | ||
|
|
3898afe81b | ||
|
|
ba9d49d0f1 | ||
|
|
a7f196999f | ||
|
|
7493cf2726 | ||
|
|
ffcfd24781 | ||
|
|
bb4f967353 | ||
|
|
8e28ce296a | ||
|
|
d3b9f4c8d8 | ||
|
|
5ebdf212e2 | ||
|
|
1bc4baffec | ||
|
|
37661ac0d0 | ||
|
|
bb7b4c6252 | ||
|
|
aeb6994974 | ||
|
|
892ed7df24 | ||
|
|
9a60203313 | ||
|
|
5ce9f79c66 | ||
|
|
0adee5121b | ||
|
|
13070aa96c | ||
|
|
b75012a0a1 | ||
|
|
09c11d1f3f | ||
|
|
b51bf8f952 | ||
|
|
9db08d508b | ||
|
|
015aa8f343 | ||
|
|
5ed5c9afbe | ||
|
|
f45174318d | ||
|
|
a59f5b4d11 | ||
|
|
2e122714b0 | ||
|
|
5beba3101e | ||
|
|
c4e0bc0a1a | ||
|
|
2a0cb56520 | ||
|
|
9e21987039 | ||
|
|
e4fc9164c1 | ||
|
|
f4ce2a9f37 | ||
|
|
21d7a8fe3f | ||
|
|
af8ae84eb5 | ||
|
|
d73699bcaa | ||
|
|
1e55407060 | ||
|
|
c9987e5efc | ||
|
|
4e90eac764 | ||
|
|
24e320237f | ||
|
|
0ca16e5996 | ||
|
|
5125ef32ab | ||
|
|
9c2c231b93 | ||
|
|
35f3949fbc | ||
|
|
03fb8b24d1 | ||
|
|
1ece44afc7 | ||
|
|
0b951527fa | ||
|
|
b2a86015f1 | ||
|
|
f0f5b97e03 | ||
|
|
ba8ec270d1 | ||
|
|
d3b94b0dd9 | ||
|
|
7f3a641629 | ||
|
|
61073dcd09 | ||
|
|
40c840446d | ||
|
|
6426106e0c | ||
|
|
799387b09b | ||
|
|
a9e0e660d2 | ||
|
|
732beaa9fd | ||
|
|
eee4322173 | ||
|
|
727920679c | ||
|
|
7ec2d0f8cb | ||
|
|
eb4b25b9e4 | ||
|
|
a3afd4c35b | ||
|
|
cfccacd21d | ||
|
|
b40302a3aa | ||
|
|
75cd2cdcab | ||
|
|
2f37ca57b4 | ||
|
|
e4446b5fa0 | ||
|
|
9519eb3a17 | ||
|
|
a1d99ba2ba | ||
|
|
5c1219f649 | ||
|
|
e2c773c416 | ||
|
|
98d16ba6e1 | ||
|
|
a8f3360c40 | ||
|
|
c5aa1d3d7e | ||
|
|
80661d3055 | ||
|
|
d4e5a1ffba | ||
|
|
7b88caee8a | ||
|
|
17f4bbdda1 | ||
|
|
6dacae86f8 | ||
|
|
55b182687e | ||
|
|
2966504fae | ||
|
|
9df426e427 | ||
|
|
f96711fc9a | ||
|
|
7d170f4f24 | ||
|
|
30350041bb | ||
|
|
bf205b0998 | ||
|
|
a065c8573f | ||
|
|
22c54a488b | ||
|
|
4b14ff7231 | ||
|
|
1bdbfdc7bf | ||
|
|
7b4812edca | ||
|
|
a6a08bb970 | ||
|
|
5b47dfbcb7 | ||
|
|
d4c54cab89 | ||
|
|
358f05ba01 | ||
|
|
71040279a4 | ||
|
|
e10843e5cb | ||
|
|
98901c4d06 | ||
|
|
12ddd7540b | ||
|
|
565971ac7f | ||
|
|
a93ae5b3e9 | ||
|
|
b662b0a7ba | ||
|
|
c26e23539b | ||
|
|
486a965624 | ||
|
|
f3f44dd65e | ||
|
|
2431e1dd20 | ||
|
|
d9eea5d5f0 | ||
|
|
9499207bae | ||
|
|
6c6ba5742f | ||
|
|
fcf1ac425e | ||
|
|
dba19e6cb7 | ||
|
|
82bb71a34b | ||
|
|
cd061cd660 | ||
|
|
ea6795df35 | ||
|
|
35edfd936b | ||
|
|
e18b3b3537 | ||
|
|
f9d749e2cb | ||
|
|
0baa952db7 | ||
|
|
fa443f0c7f | ||
|
|
98b8c2b43a | ||
|
|
f5b0fa0467 | ||
|
|
67608ae02a | ||
|
|
73495c4a76 | ||
|
|
3ae5b4cd80 | ||
|
|
6b4de9833f | ||
|
|
4531658dd8 | ||
|
|
11e738e6c6 | ||
|
|
f531baf7d3 | ||
|
|
f4e791b6f1 | ||
|
|
c05cd7b2f6 | ||
|
|
9778fe7dfd | ||
|
|
00208b69a7 | ||
|
|
3df8397c9a | ||
|
|
b480c2895c | ||
|
|
928b4fa887 | ||
|
|
4a8ff10965 | ||
|
|
23df025313 | ||
|
|
aa4ee6ec6b | ||
|
|
a0ca0c47c9 | ||
|
|
775879096f | ||
|
|
8969d07d45 | ||
|
|
b1fd15c083 | ||
|
|
b9f4a9abda | ||
|
|
fc5230a9b9 | ||
|
|
9f6f9ce7bb | ||
|
|
b9d5cc4ae7 | ||
|
|
cfed3ee53a | ||
|
|
a242fbd8a2 | ||
|
|
b1a8accfd0 | ||
|
|
905d64cb51 | ||
|
|
ab2d7ae029 | ||
|
|
2b33f588bd | ||
|
|
3c86a85cd3 | ||
|
|
b4ae3c97a3 | ||
|
|
80dd6a2efa | ||
|
|
5069854924 | ||
|
|
29d4875778 | ||
|
|
df44f00eb8 | ||
|
|
1a08cd6ae4 | ||
|
|
2f39539135 | ||
|
|
8350c398f3 | ||
|
|
3591759cdb | ||
|
|
f352704fcd | ||
|
|
679c0fea03 | ||
|
|
370927e489 | ||
|
|
d675097da5 | ||
|
|
e4c7797102 | ||
|
|
57a9c54630 | ||
|
|
9c885af7ec | ||
|
|
242d293f5a | ||
|
|
bc44426d9d | ||
|
|
e4441f9126 | ||
|
|
97855b563d | ||
|
|
7ef5462969 | ||
|
|
714dbdc930 | ||
|
|
f1366a5e8a | ||
|
|
5af7659883 | ||
|
|
05df1adb11 | ||
|
|
05066496d8 | ||
|
|
599821c018 | ||
|
|
52cae0e723 | ||
|
|
ceb9180bbe | ||
|
|
657beeb63d | ||
|
|
16a915faa1 | ||
|
|
2ca3d67eb5 | ||
|
|
309cf6658d | ||
|
|
e21aac63f4 | ||
|
|
9a0b240e73 | ||
|
|
046f3a6458 | ||
|
|
398a3ed1ea | ||
|
|
f1ab723957 | ||
|
|
9380a06a35 | ||
|
|
73131ba99e | ||
|
|
12e813a5a1 | ||
|
|
de87fcb123 | ||
|
|
26b4eb000a | ||
|
|
4c9dacbcbe | ||
|
|
29a959fb59 | ||
|
|
b5049bbb8d | ||
|
|
42ab52e791 | ||
|
|
999070fd9b | ||
|
|
03343276c2 | ||
|
|
02e7a8920a | ||
|
|
fbc2d6ad7e | ||
|
|
7ce642fa7b | ||
|
|
8c984d6dbe | ||
|
|
9c0dad1342 | ||
|
|
dc69975b87 | ||
|
|
da06932d85 | ||
|
|
4cfed1e438 | ||
|
|
8c461edc1d | ||
|
|
ed7d5aa8af | ||
|
|
66a4b444e3 | ||
|
|
cebcd25f81 | ||
|
|
48ba246fc4 | ||
|
|
16a2e75304 | ||
|
|
eb1d4a6354 | ||
|
|
490abc5826 | ||
|
|
90c78a469d | ||
|
|
3225112fc7 | ||
|
|
20732ab927 | ||
|
|
501a0ce009 | ||
|
|
95af0e1ef5 | ||
|
|
92a7c1a20a | ||
|
|
c51abaaaf4 | ||
|
|
1df24c0287 | ||
|
|
22e6603a5b | ||
|
|
8c07e0b89f | ||
|
|
4be5d02fd7 | ||
|
|
d6176eec0d | ||
|
|
2279087023 | ||
|
|
59197f2ac9 | ||
|
|
2af8c51798 | ||
|
|
a05fbe5cd8 | ||
|
|
30f12050dc | ||
|
|
fdc97228e7 | ||
|
|
ca9266f772 | ||
|
|
d1eeeedca1 | ||
|
|
39486f078c | ||
|
|
c6904e740c | ||
|
|
626279667f | ||
|
|
c1d8cccdb3 | ||
|
|
db8a191afb | ||
|
|
e8099b8492 | ||
|
|
5bcea269de | ||
|
|
9fe331d11a | ||
|
|
cbeb5ced09 | ||
|
|
03ef46b316 | ||
|
|
66de7141d9 | ||
|
|
8f80f4d439 | ||
|
|
4b30a7eca1 | ||
|
|
5ee58d727a | ||
|
|
f8f03a57ba | ||
|
|
34618e957c | ||
|
|
f745ba9f5d | ||
|
|
1677e1eeb1 | ||
|
|
eebbb45b2c | ||
|
|
ada5fa5056 | ||
|
|
df6860294d | ||
|
|
8b97275195 | ||
|
|
7dfde0aba3 | ||
|
|
517b1ae328 | ||
|
|
7e92f73a30 | ||
|
|
1514ad0694 | ||
|
|
48bebfef05 | ||
|
|
19cb78e529 | ||
|
|
ddb66257ce | ||
|
|
fded944458 | ||
|
|
e55d5f8199 | ||
|
|
e8dc32489d | ||
|
|
f8ec884788 | ||
|
|
fa0fb3e519 | ||
|
|
023bc56986 | ||
|
|
c52050e37c | ||
|
|
a523f3c130 | ||
|
|
0126e6dae3 | ||
|
|
2a991e219f | ||
|
|
3c31e29a48 | ||
|
|
e7a143e181 | ||
|
|
03b37b049b | ||
|
|
1563cb4693 | ||
|
|
82f32154fd | ||
|
|
37b13aa484 | ||
|
|
cf00e02c12 | ||
|
|
4bfb8a840a | ||
|
|
490efdbd5a | ||
|
|
d2335d2382 | ||
|
|
7e8a729fed | ||
|
|
d6274b42af | ||
|
|
699a502cc5 | ||
|
|
292bfed82a | ||
|
|
a34847ab7b | ||
|
|
9e5f7f943b | ||
|
|
7dc9afddb3 | ||
|
|
f174d73098 | ||
|
|
15c90a00cd | ||
|
|
1ecb6728b1 | ||
|
|
f13c38e135 | ||
|
|
7c34056281 | ||
|
|
0fb1c82dc0 | ||
|
|
ecd8e8838f | ||
|
|
cd969babff | ||
|
|
116a24ac23 | ||
|
|
a951e4eaa0 | ||
|
|
fa820d4e07 | ||
|
|
f0ab756a5d | ||
|
|
6545960635 | ||
|
|
ef8bb689d7 | ||
|
|
ce988a66df | ||
|
|
54d98f710c | ||
|
|
c1ab52e38f | ||
|
|
42f6c88463 | ||
|
|
d85e43a427 | ||
|
|
d74f571ddb | ||
|
|
cfcbde62b2 | ||
|
|
f3fbb82dbd | ||
|
|
0a0c9d8070 | ||
|
|
27e22ace02 | ||
|
|
5b7197fbd6 | ||
|
|
a1764bf043 | ||
|
|
cfc280274f | ||
|
|
258953ec53 | ||
|
|
236d528923 | ||
|
|
3fd6c7b688 | ||
|
|
3306fb4352 | ||
|
|
c0960c2250 | ||
|
|
651fd5e5a2 | ||
|
|
4d72113af4 | ||
|
|
ca6fe5105c | ||
|
|
27c19645ac | ||
|
|
259086a979 | ||
|
|
9f4ba4b50c | ||
|
|
21ad3cfc5e | ||
|
|
bfba7a5102 | ||
|
|
0119fcd24a | ||
|
|
39edeacc5b | ||
|
|
e0dcec7d5b | ||
|
|
3ec7071889 | ||
|
|
eeb411bacb | ||
|
|
03d13ebbc1 | ||
|
|
46bb7df90e | ||
|
|
69ed6496a6 | ||
|
|
b100daea1b | ||
|
|
ec38900386 | ||
|
|
a523cdf1e6 | ||
|
|
c552ef1616 | ||
|
|
752a4413f3 | ||
|
|
186bd2d7da | ||
|
|
610c2f98ce | ||
|
|
fdac56a9a7 | ||
|
|
2bb8c97a5a | ||
|
|
ad3b034fd2 | ||
|
|
b2b1657e31 | ||
|
|
3a4087a2e1 | ||
|
|
3fe7d3adf3 | ||
|
|
f64566dbae | ||
|
|
98f3a50fbd | ||
|
|
ef5234d734 | ||
|
|
5b34571cf2 | ||
|
|
edac489791 | ||
|
|
acb01268da | ||
|
|
9b19a88a14 | ||
|
|
555a8bfed7 | ||
|
|
b4efa372b2 | ||
|
|
8ee5a5fa41 | ||
|
|
c77f70b371 | ||
|
|
c4f1721250 | ||
|
|
32d2f98bef | ||
|
|
1f40291c1b | ||
|
|
8b3dda264b | ||
|
|
1a5c749b15 | ||
|
|
0afd72195b | ||
|
|
fef2c873d5 | ||
|
|
7d0fdfff24 | ||
|
|
2d966bc535 | ||
|
|
4b3c8b555e | ||
|
|
c0de6f7721 | ||
|
|
70323eccd0 | ||
|
|
b259c66530 | ||
|
|
84bbd884ae | ||
|
|
fb3867c21c | ||
|
|
0b61e6b4b6 | ||
|
|
fbad22131a | ||
|
|
884232bd33 | ||
|
|
70acd483d1 | ||
|
|
018699cc39 | ||
|
|
61d1786a00 | ||
|
|
07c38892f9 | ||
|
|
f689d44f01 | ||
|
|
ce53c8d84a | ||
|
|
d1030df71a | ||
|
|
d9be047a5c | ||
|
|
2966a22521 | ||
|
|
79d5f9b46d | ||
|
|
9f387d1ddf | ||
|
|
f32a1455fd | ||
|
|
1dcc84a894 | ||
|
|
1309a14ea0 | ||
|
|
04010be62a | ||
|
|
d8319fd89e | ||
|
|
0dbd8155ae | ||
|
|
f426c6be3d | ||
|
|
88d32d6c4a | ||
|
|
784e8ba0eb | ||
|
|
196ea025a9 | ||
|
|
654f64b604 | ||
|
|
b568fdd47b | ||
|
|
45e72f9202 | ||
|
|
4f71f79bb4 | ||
|
|
9aa52d7094 | ||
|
|
267dfa7545 | ||
|
|
f8a086a26b | ||
|
|
c78689db89 | ||
|
|
44b4e699aa | ||
|
|
e3a88484c6 | ||
|
|
f2e710f94d | ||
|
|
7df35a6203 | ||
|
|
5558692821 | ||
|
|
47db405dd5 | ||
|
|
47faabd7f6 | ||
|
|
409dd8c250 | ||
|
|
1b995b1c0d | ||
|
|
1a48640042 | ||
|
|
9d3a944033 | ||
|
|
f1ea2abcc5 | ||
|
|
108c0662ca | ||
|
|
4ad15366ae | ||
|
|
1c53168e12 | ||
|
|
df5ded6050 | ||
|
|
52dfa47d6d | ||
|
|
2df22b5a42 | ||
|
|
a6a151f98f | ||
|
|
ec645d7f0e | ||
|
|
1d2666c598 | ||
|
|
85a9990f50 | ||
|
|
6a681094f8 | ||
|
|
c8ee474025 | ||
|
|
8b4da360c1 | ||
|
|
313ede1465 | ||
|
|
2bc8120186 | ||
|
|
068dce1b56 | ||
|
|
7a110a80b5 | ||
|
|
a1da50a1dd | ||
|
|
8bfe0746dd | ||
|
|
5e625dc08d | ||
|
|
e16d885d30 | ||
|
|
369405e1f5 | ||
|
|
d34e40ea29 | ||
|
|
759a9bed7a | ||
|
|
a7e0522cf9 | ||
|
|
44f819e9be | ||
|
|
f55566956e | ||
|
|
9d9ae41006 | ||
|
|
b19a6ae0d4 | ||
|
|
d4996103c5 | ||
|
|
ef4ef398ed | ||
|
|
82b6055a6d | ||
|
|
994dfe1366 | ||
|
|
6e71b2f120 | ||
|
|
26bea083af | ||
|
|
800786f314 | ||
|
|
9ff7adab79 | ||
|
|
aa4c4acd6e | ||
|
|
345cece010 | ||
|
|
ac00afd142 | ||
|
|
585e97538c | ||
|
|
cfcd4e9404 | ||
|
|
5865604e0f | ||
|
|
398c8026df | ||
|
|
594c22b9f9 | ||
|
|
b91d3a38f3 | ||
|
|
d4ddd8114c | ||
|
|
02bf1b283d | ||
|
|
aa846eed7d | ||
|
|
487900ea58 | ||
|
|
6d93668df5 | ||
|
|
4c73af527c | ||
|
|
73f02b8c30 | ||
|
|
1d51805181 | ||
|
|
b41dd6ff64 | ||
|
|
e3f9bf17f6 | ||
|
|
7a6ea2f2db | ||
|
|
ffe6d0b406 | ||
|
|
3bff036d04 | ||
|
|
b4ee92ea28 | ||
|
|
e49147f5c2 | ||
|
|
7aa3910911 | ||
|
|
16f0be2758 | ||
|
|
732bc9be73 | ||
|
|
235abb58f4 | ||
|
|
5ff00b2c5c | ||
|
|
a0d37f7edd | ||
|
|
04a0d0c04c | ||
|
|
49beec26c5 | ||
|
|
7686ef0d73 | ||
|
|
c2d8fb9da2 | ||
|
|
9d6afb9f55 | ||
|
|
de805f68c5 | ||
|
|
79a1ed3d03 | ||
|
|
5a451977a4 | ||
|
|
50e482df9a | ||
|
|
576697993e | ||
|
|
7adc31890c | ||
|
|
afa42a53cd | ||
|
|
3cb2b09dd6 | ||
|
|
35213d720e | ||
|
|
dd0b8b94fa | ||
|
|
152587e5c4 | ||
|
|
70c98f6cea | ||
|
|
288c1d8867 | ||
|
|
a9ab0c7fc6 | ||
|
|
af89ddf40e | ||
|
|
57c7e32361 | ||
|
|
00779c550c | ||
|
|
6f128f61e1 | ||
|
|
9ad1c5d2e7 | ||
|
|
d06d797ab1 | ||
|
|
14ae68b38c | ||
|
|
85a31e7f04 | ||
|
|
003081508c | ||
|
|
e62d8e2bdc | ||
|
|
2cacb05e66 | ||
|
|
8c4f6650c2 | ||
|
|
5da85b079a | ||
|
|
0c9a29000e | ||
|
|
412b7501e5 | ||
|
|
87ffcf737f | ||
|
|
10a83ed994 | ||
|
|
b9da0fa6bd | ||
|
|
b2957a56a8 | ||
|
|
e54cef963b | ||
|
|
410e562888 | ||
|
|
58a2cb6258 | ||
|
|
c658c937a0 | ||
|
|
217f78a5e8 | ||
|
|
8d3c817195 | ||
|
|
9ce5fc4f58 | ||
|
|
b8eff3db00 | ||
|
|
cbe7be7d20 | ||
|
|
021f482aea | ||
|
|
24b371d58a | ||
|
|
010edda2cd | ||
|
|
a2ecdabe8d | ||
|
|
deda7f13d1 | ||
|
|
1152f3f490 | ||
|
|
89151422ba | ||
|
|
0d6ae53860 | ||
|
|
55d0eb904d | ||
|
|
ec1f2aa35a | ||
|
|
f3a6b9f536 | ||
|
|
4f60bcc16d | ||
|
|
2255ebd533 | ||
|
|
1ebf2f5003 | ||
|
|
2a55d1ea03 | ||
|
|
4d9c988059 | ||
|
|
ef0225c1bd | ||
|
|
633fec0094 | ||
|
|
d5c9a9c34a | ||
|
|
a547bbc815 | ||
|
|
2af5a26ca3 | ||
|
|
24b61319ca | ||
|
|
560b18ab14 | ||
|
|
2bee73487a | ||
|
|
767df4ba4f | ||
|
|
93522443a4 | ||
|
|
af20f7b37c | ||
|
|
795c0c2673 | ||
|
|
b97e89754b | ||
|
|
80ad9ec410 | ||
|
|
ba9fb93a7b | ||
|
|
cb461a2304 | ||
|
|
b4f6dbdeb6 | ||
|
|
ab9092d53e | ||
|
|
b741c594e2 | ||
|
|
01973b0712 | ||
|
|
00ffcc50d8 | ||
|
|
e1503691f9 | ||
|
|
f8ecb5298f | ||
|
|
f83c4a36b5 | ||
|
|
f839c1c41c | ||
|
|
f94b44b65f | ||
|
|
928ac60b42 | ||
|
|
9bf9c167a7 | ||
|
|
fd63bda12a | ||
|
|
bde583d381 | ||
|
|
6a6050dea2 | ||
|
|
355ab88ba9 | ||
|
|
adc22f97ff | ||
|
|
fdd53c2668 | ||
|
|
37e3dac97f | ||
|
|
ac99a905ec | ||
|
|
5b91a68b2f | ||
|
|
5b2ceef0aa | ||
|
|
2c54c811d5 | ||
|
|
55a8d9a552 | ||
|
|
93227be07e | ||
|
|
42adaacb04 | ||
|
|
0b6cbb0c43 | ||
|
|
37568e0410 | ||
|
|
eba08dd086 | ||
|
|
3472ce4675 | ||
|
|
0465095bbc | ||
|
|
6134d2585c | ||
|
|
344643ffdd | ||
|
|
4bfc8bb117 | ||
|
|
cce1d2f361 | ||
|
|
ab68c32549 | ||
|
|
0c6275777e | ||
|
|
0ae4cd25b6 | ||
|
|
8d030f3fac | ||
|
|
e6df31e338 | ||
|
|
6a09f78fc5 | ||
|
|
57844e8c7c | ||
|
|
845d67db52 | ||
|
|
4f5de217e7 | ||
|
|
39d18a48c8 | ||
|
|
0bea980aa7 | ||
|
|
b0e09f8cff | ||
|
|
299ab754b3 | ||
|
|
cada68dee5 | ||
|
|
70c2d18abe | ||
|
|
dd34b9bf0f | ||
|
|
4c52a82e85 | ||
|
|
99f30eb07e | ||
|
|
7f67bfb2df | ||
|
|
4a2c0a2536 | ||
|
|
019685ef9b | ||
|
|
33b29ca55d | ||
|
|
e9d962388d | ||
|
|
913a6ec653 | ||
|
|
d2c2138be6 | ||
|
|
4bb0b15adc | ||
|
|
6aa57212a9 | ||
|
|
5a30bc849e | ||
|
|
995c5705e5 | ||
|
|
c580b15643 | ||
|
|
9a97bc6824 | ||
|
|
9c58d20320 | ||
|
|
210e06fd62 | ||
|
|
cb085223b3 | ||
|
|
b8954ef27b | ||
|
|
b7ca99dd55 | ||
|
|
191783d30c | ||
|
|
0ba70bb4f7 | ||
|
|
277f63609c | ||
|
|
cd01b7ba6b | ||
|
|
18a25447a6 | ||
|
|
93468a849c | ||
|
|
bc864fa71e | ||
|
|
63e46dcf12 | ||
|
|
3ac1ddb51e | ||
|
|
18d3b1ffa8 | ||
|
|
3c713fb362 | ||
|
|
c74a577030 | ||
|
|
a849c03651 | ||
|
|
bf8be3096d | ||
|
|
34a5375a21 | ||
|
|
2303f414bb | ||
|
|
8331e86624 | ||
|
|
c591902722 | ||
|
|
36a478d78d | ||
|
|
6f278cead4 | ||
|
|
6fe7063664 | ||
|
|
c1f903afcd | ||
|
|
2344bf696e | ||
|
|
ba93f2a431 | ||
|
|
15b3698619 | ||
|
|
10060a66f8 | ||
|
|
3ccc53a63c | ||
|
|
cd120bcad4 | ||
|
|
d282f07720 | ||
|
|
1d66df366f | ||
|
|
3a0f0cdfa8 | ||
|
|
ed291cfe4d | ||
|
|
eec5a085e8 | ||
|
|
44ebba25e5 | ||
|
|
eb484df801 | ||
|
|
a3b84f0876 | ||
|
|
3aaa7345f6 | ||
|
|
7559f7c2f9 | ||
|
|
e48e7aa544 | ||
|
|
55a71ea46a | ||
|
|
6879729b01 | ||
|
|
ad5f9232f7 | ||
|
|
45c44fd936 | ||
|
|
f7cc0dbd00 | ||
|
|
0bf2cc8442 | ||
|
|
23c8590123 | ||
|
|
5ad15655a9 | ||
|
|
59249a7155 | ||
|
|
7f545935b4 | ||
|
|
fe9001a550 | ||
|
|
313bed630a | ||
|
|
d030b09c51 | ||
|
|
454557a468 | ||
|
|
1c7a1e3d8f | ||
|
|
e3e937354b | ||
|
|
83936a924e | ||
|
|
0c74043668 | ||
|
|
4a7cee412c | ||
|
|
b7972342dc | ||
|
|
7e0f5e1401 | ||
|
|
d55ced465d | ||
|
|
8d2bf7571a | ||
|
|
764da32cba | ||
|
|
1d6a015939 | ||
|
|
68a9f55f53 | ||
|
|
e638e7f2c9 | ||
|
|
d3aef40bb3 | ||
|
|
e081e66a13 | ||
|
|
7f055ddb4b | ||
|
|
79f8704a00 | ||
|
|
df3b938ed0 | ||
|
|
cf27269f08 | ||
|
|
4d0b02e049 | ||
|
|
f8bb07ec35 | ||
|
|
601d789662 | ||
|
|
54b7e8f1b1 | ||
|
|
33df962e70 | ||
|
|
ab894eeca3 | ||
|
|
bf4e21933c | ||
|
|
0ea254e94c | ||
|
|
94ae69813f | ||
|
|
f6782893e7 | ||
|
|
54ba881dd2 | ||
|
|
2cd7cece85 | ||
|
|
74e2bcfc37 | ||
|
|
d3f5758df8 | ||
|
|
da052c02e2 | ||
|
|
f063f52981 | ||
|
|
2646d7e656 | ||
|
|
0e22f73d11 | ||
|
|
2d7ada5106 | ||
|
|
c8524485bb | ||
|
|
191a8e4e43 | ||
|
|
4f3e058d64 | ||
|
|
da33b4f406 | ||
|
|
d5851fc4ed | ||
|
|
de9adadf04 | ||
|
|
08f1ba8186 | ||
|
|
135239eabe | ||
|
|
ec838bb8a1 | ||
|
|
52bc9b6326 | ||
|
|
e7eff63585 | ||
|
|
21222511dd | ||
|
|
ffb7924585 | ||
|
|
990f7ab9dc | ||
|
|
30f85737c8 | ||
|
|
f1f196585d | ||
|
|
7bbeed304f | ||
|
|
bdbe7b25eb | ||
|
|
d6e3576203 | ||
|
|
c0411273a9 | ||
|
|
789cae29eb | ||
|
|
d668d9c3bb | ||
|
|
4b8effe250 | ||
|
|
896a9a0a23 | ||
|
|
a28929bc36 | ||
|
|
59606709a6 | ||
|
|
5398d53408 | ||
|
|
31e272711f | ||
|
|
b12abc4533 | ||
|
|
628430f0e9 | ||
|
|
1f3501d8de | ||
|
|
7e3858ef31 | ||
|
|
b8613c5cdf | ||
|
|
8b8bb25717 | ||
|
|
3871508385 | ||
|
|
32a5f4389b | ||
|
|
fadaedc382 | ||
|
|
1772faae3f | ||
|
|
37d4245363 | ||
|
|
2e8bb882eb | ||
|
|
73834a4455 | ||
|
|
abf7382520 | ||
|
|
6ebfe9b630 | ||
|
|
310577f0c2 | ||
|
|
2fe6482730 | ||
|
|
10c285f081 | ||
|
|
9d1b5833a9 | ||
|
|
eb1234832e | ||
|
|
3eac899af5 | ||
|
|
5195708028 | ||
|
|
00977986df | ||
|
|
a5887f7dd6 | ||
|
|
05cc118282 | ||
|
|
cb42cf6ceb | ||
|
|
8f4dd0225b | ||
|
|
e15e7d5ecc | ||
|
|
02d9eaa21b | ||
|
|
982a1c1fd3 | ||
|
|
d2067be9d1 | ||
|
|
2a46a6a9d0 | ||
|
|
816c5bb040 | ||
|
|
356d9170f0 | ||
|
|
0d5afe48a8 | ||
|
|
0eac173451 | ||
|
|
355c2e78ec | ||
|
|
4722471b08 | ||
|
|
1eed542f46 | ||
|
|
bb1444b7f9 | ||
|
|
4c7ab1a47f | ||
|
|
b67a84800b | ||
|
|
7d3ffe324a | ||
|
|
f6ef39867c | ||
|
|
7a3b82e40b | ||
|
|
c3c847c5ff | ||
|
|
2e4ec0e402 | ||
|
|
cca6e94a71 | ||
|
|
20addc10ac | ||
|
|
2a49c009c0 | ||
|
|
077e4916fc | ||
|
|
ae21ba28c7 | ||
|
|
d5cf59cb16 | ||
|
|
ba3bb79b52 | ||
|
|
4aed00d954 | ||
|
|
1052537fc6 | ||
|
|
6d30afb166 | ||
|
|
60c2ab84d8 | ||
|
|
4d9b270d3a | ||
|
|
86bf9f2a28 | ||
|
|
ed1e99353f | ||
|
|
4d7749e265 | ||
|
|
368e3c36b9 | ||
|
|
ef44922aa6 | ||
|
|
84abcc565d | ||
|
|
b3b54ffe00 | ||
|
|
43dae4915f | ||
|
|
990f93a34e | ||
|
|
17b1d4004a | ||
|
|
41f0012dc1 | ||
|
|
9a3282285c | ||
|
|
2ad63e7d7f | ||
|
|
abaf65e185 | ||
|
|
a16b8e3ffc | ||
|
|
fdebee9011 | ||
|
|
00d76439b2 | ||
|
|
b90694142c | ||
|
|
3dac462afd | ||
|
|
a8ffebce60 | ||
|
|
4a86221e8a | ||
|
|
7679b05c55 | ||
|
|
1efd93e947 | ||
|
|
9641af3973 | ||
|
|
953c62f36c | ||
|
|
7620b42bef | ||
|
|
f35d57b534 | ||
|
|
10aadd4e66 | ||
|
|
0af795d2e0 | ||
|
|
80b403c881 | ||
|
|
5d3db0dbd3 | ||
|
|
d015c35912 | ||
|
|
c811662d6f | ||
|
|
8e1b3185fe | ||
|
|
d892f396fb | ||
|
|
64dfce26b1 | ||
|
|
d375d5588c | ||
|
|
e1fb9121af | ||
|
|
7101dc02c4 | ||
|
|
8d3067a625 | ||
|
|
5b85dacfa4 | ||
|
|
4ea3851fb4 | ||
|
|
6071121323 | ||
|
|
e1fe8c8e28 | ||
|
|
708b41b8da | ||
|
|
ec730735bc | ||
|
|
0e2c6131f6 | ||
|
|
1172a542fd | ||
|
|
4b71ea18b8 | ||
|
|
4d8d51facc | ||
|
|
33cbbbd162 | ||
|
|
71fb359aec | ||
|
|
879ce2a195 | ||
|
|
e3d57641ca | ||
|
|
6bd754e6d6 | ||
|
|
c419b8d2a6 | ||
|
|
77191443ec | ||
|
|
0c1b3939ea | ||
|
|
088dc4eb99 | ||
|
|
3019641d9f | ||
|
|
82bb17ebf9 | ||
|
|
67ee42dcda | ||
|
|
0387d9a82e | ||
|
|
bb22cc6ba9 | ||
|
|
9d78336096 | ||
|
|
07590fba08 | ||
|
|
f280ba4e05 | ||
|
|
6e111aab3a | ||
|
|
5fad51e876 | ||
|
|
df11323434 | ||
|
|
2c98474e16 | ||
|
|
6e2803a614 | ||
|
|
6ea8469189 | ||
|
|
0a8064d7b5 | ||
|
|
ad294fa15a | ||
|
|
bebeb43a25 | ||
|
|
72600d2e84 | ||
|
|
193d740133 | ||
|
|
2e4278a89e | ||
|
|
2717c0d494 | ||
|
|
cf3dac5a7d | ||
|
|
56ca218826 | ||
|
|
7cbef74401 | ||
|
|
a228e18669 | ||
|
|
3f24909423 | ||
|
|
72c7cb38b6 | ||
|
|
54983e52c5 | ||
|
|
6ddf9c6817 | ||
|
|
3c20f384f5 | ||
|
|
f9f4a27c6a | ||
|
|
ef8af1a2b6 | ||
|
|
3f4839cfe5 | ||
|
|
7be40800c1 | ||
|
|
78938459b5 | ||
|
|
e2ef4b497c | ||
|
|
d78b59b9cd | ||
|
|
1908ad7583 | ||
|
|
b20da90917 | ||
|
|
69d5be5bd9 | ||
|
|
8e8ee4bb5c | ||
|
|
7bb5f45c82 | ||
|
|
b074af6d41 | ||
|
|
5038d5e838 | ||
|
|
da234f5fcf | ||
|
|
6ff8ba6682 | ||
|
|
dfc630aa23 | ||
|
|
74059bda82 | ||
|
|
394a322fbe | ||
|
|
46786907b6 | ||
|
|
b11179bc84 | ||
|
|
d8115e1cec | ||
|
|
52a26ba4a5 | ||
|
|
72c7a54879 | ||
|
|
9c66737813 | ||
|
|
4f159471d7 | ||
|
|
e63d418b03 | ||
|
|
5f3f859b81 | ||
|
|
90cffa7b37 | ||
|
|
770a7d20a8 | ||
|
|
6b04ca78f9 | ||
|
|
e5ad11748f | ||
|
|
fa38216eaa | ||
|
|
c94e8a395f | ||
|
|
dddd7972fb | ||
|
|
3e4323d10c | ||
|
|
197ca7cf4a | ||
|
|
41827bb653 | ||
|
|
e7656f3be5 | ||
|
|
17f10cafbc | ||
|
|
62239488c9 | ||
|
|
615abebe99 | ||
|
|
b703546c80 | ||
|
|
c862679aac | ||
|
|
2b2bfa6389 | ||
|
|
8e50d42099 | ||
|
|
2fd6fde9b5 | ||
|
|
47606cf46c | ||
|
|
2ab15ba0d1 | ||
|
|
0bd68b9b3e | ||
|
|
6cfb6f60f5 | ||
|
|
be1886179f | ||
|
|
65f2368050 | ||
|
|
90a8a2e9da | ||
|
|
9afd057ee2 | ||
|
|
0440c2a89c | ||
|
|
7245506564 | ||
|
|
8c7f10b976 | ||
|
|
cb2fb9e1c1 | ||
|
|
1316cc6c32 | ||
|
|
ddd503fc31 | ||
|
|
ded1fe3db5 | ||
|
|
f6dc60a9f0 | ||
|
|
62e018aced | ||
|
|
799b31f2aa | ||
|
|
89725523d0 | ||
|
|
c14fff73b2 | ||
|
|
81b4156744 | ||
|
|
35a02524c9 | ||
|
|
a0b2bbcb1c | ||
|
|
baf16a2719 | ||
|
|
de15debd11 | ||
|
|
5253f90284 | ||
|
|
46f4aa8e35 | ||
|
|
8d58e317aa | ||
|
|
046836a63a | ||
|
|
92930d7a6f | ||
|
|
b49d1f3f39 | ||
|
|
cba2fb93c9 | ||
|
|
4c40598c79 | ||
|
|
69c36da984 | ||
|
|
a75835f664 | ||
|
|
06eb8e3e43 | ||
|
|
68d3a7cdb2 | ||
|
|
fa443cea0d | ||
|
|
da1645c353 | ||
|
|
1d498a5ba3 | ||
|
|
f3876079a9 | ||
|
|
dc390b796d | ||
|
|
09edb071ff | ||
|
|
13d28d6ecb | ||
|
|
b92eaa14a5 | ||
|
|
10c41c9dfa | ||
|
|
6c6e26bcaa | ||
|
|
a75ad8080d | ||
|
|
1834182d2e | ||
|
|
a395adc2b6 | ||
|
|
5fe795059b | ||
|
|
292df0c8de | ||
|
|
c3e752f98f | ||
|
|
56737375da | ||
|
|
5fe182f3c4 | ||
|
|
3242d396a9 | ||
|
|
390a23d88e | ||
|
|
00ddfed039 | ||
|
|
775588b300 | ||
|
|
8a50df0e06 | ||
|
|
9c5a38ac70 | ||
|
|
ce7a8bb1f1 | ||
|
|
77dcb26372 | ||
|
|
0aecaec778 | ||
|
|
90b48b5447 | ||
|
|
89be097c3f | ||
|
|
4fdb9ef7f9 | ||
|
|
68dba80b9c | ||
|
|
13615dc15a | ||
|
|
8820a45312 | ||
|
|
b5186a367a | ||
|
|
24ee2cba60 | ||
|
|
ade7bed52f | ||
|
|
3c3ab8138b | ||
|
|
85c1fc457c | ||
|
|
9525581631 | ||
|
|
9856588fa8 | ||
|
|
eb85f56534 | ||
|
|
8cce641e43 | ||
|
|
be237b402c | ||
|
|
26b9fa20be | ||
|
|
9c7efa9037 | ||
|
|
2fa0e11ebb | ||
|
|
db40ad80ae | ||
|
|
11262acafc | ||
|
|
b41147b7f6 | ||
|
|
de9c9ff3f3 | ||
|
|
7a0d57997d | ||
|
|
74aa0a4917 | ||
|
|
c39bf4ee34 | ||
|
|
25edd60ac5 | ||
|
|
28a94982fe | ||
|
|
7c1adf1c2a | ||
|
|
f6dc001e3a | ||
|
|
bff37b60d5 | ||
|
|
f8b5e238f4 | ||
|
|
56de452158 | ||
|
|
0d18289699 | ||
|
|
7819893f81 | ||
|
|
7073eb5494 | ||
|
|
9996b26eba | ||
|
|
d2e205a3fc | ||
|
|
ae1e2dbbcb | ||
|
|
4105614645 | ||
|
|
3753d989b7 | ||
|
|
fb2d08032d | ||
|
|
1dcd260feb | ||
|
|
fc3ae7bfe9 | ||
|
|
dd9715761c | ||
|
|
2b35f0d714 | ||
|
|
18a84c0c6e | ||
|
|
39a88cd4d1 | ||
|
|
502a087962 | ||
|
|
9de9d4ca53 | ||
|
|
d5a2ef88df | ||
|
|
abcb758a56 | ||
|
|
2c88d952d2 | ||
|
|
373ce84268 | ||
|
|
9aff5237ea | ||
|
|
58a333ef99 | ||
|
|
5506af61ab | ||
|
|
56df11005b | ||
|
|
f6f80da995 | ||
|
|
8d5f633884 | ||
|
|
15b3599a05 | ||
|
|
f089ef0c60 | ||
|
|
c513f93329 | ||
|
|
00f55f4044 | ||
|
|
7741d9f65d | ||
|
|
b6418401b3 | ||
|
|
b7d6f32b89 | ||
|
|
789af07ebe | ||
|
|
33981efab1 | ||
|
|
0d05da4e0a | ||
|
|
97c75209f3 | ||
|
|
bd7d643f07 | ||
|
|
ee8049c3bf | ||
|
|
6821546d51 | ||
|
|
c188296a82 | ||
|
|
19d93f9e34 | ||
|
|
a8b9c92207 | ||
|
|
eaa5a35f47 | ||
|
|
c12c5939c0 | ||
|
|
4174317f57 | ||
|
|
5f508e5d17 | ||
|
|
8eedf4b3f1 | ||
|
|
786650f660 | ||
|
|
dea4a38a6c | ||
|
|
7298cbbe0d | ||
|
|
a0f4ac195d | ||
|
|
7ee57a381a |
@@ -1,9 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
|
||||
30931
.gitattributes
vendored
30931
.gitattributes
vendored
File diff suppressed because it is too large
Load Diff
30
.gitignore
vendored
30
.gitignore
vendored
@@ -2,20 +2,32 @@
|
||||
/*.iml
|
||||
/*.tmp
|
||||
/.metadata
|
||||
forge-ai/forge-ai.iml
|
||||
forge-ai/target
|
||||
forge-core/forge-core.iml
|
||||
forge-core/target
|
||||
forge-game/target
|
||||
forge-gui/forge-gui.iml
|
||||
forge-gui/forge.profile.properties
|
||||
forge-gui/res/*.log
|
||||
forge-gui/res/PerSetTrackingResults
|
||||
forge-gui/res/cardsfolder/*.bat
|
||||
forge-gui/res/decks
|
||||
forge-gui/res/layouts
|
||||
forge-gui/res/pics*
|
||||
forge-gui/res/pics_product
|
||||
forge-gui/target
|
||||
forge-gui/tools/PerSetTrackingResults
|
||||
forge-gui/tools/oracleScript.log
|
||||
forge-m-android/bin
|
||||
forge-m-base/bin
|
||||
forge-m-desktop/bin
|
||||
forge-net/target
|
||||
/forge.profile.properties
|
||||
/nbactions.xml
|
||||
/pom.xml.next
|
||||
/pom.xml.releaseBackup
|
||||
/pom.xml.tag
|
||||
/release.properties
|
||||
res/*.log
|
||||
res/PerSetTrackingResults
|
||||
res/cardsfolder/*.bat
|
||||
res/decks
|
||||
res/layouts
|
||||
res/pics*
|
||||
res/pics_product
|
||||
/target
|
||||
/test-output
|
||||
tools/PerSetTrackingResults
|
||||
tools/oracleScript.log
|
||||
|
||||
29
.project
29
.project
@@ -4,36 +4,7 @@
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.pde.ManifestBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.pde.SchemaBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>net.sf.eclipsecs.core.CheckstyleBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>net.sf.eclipsecs.core.CheckstyleNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#Wed Jul 27 18:40:11 EDT 2011
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.6
|
||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.source=1.6
|
||||
org.eclipse.jdt.core.compiler.source=1.7
|
||||
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
|
||||
|
||||
219
CHANGES.txt
219
CHANGES.txt
@@ -1,219 +0,0 @@
|
||||
Forge Beta: 06-14-2013 ver 1.4.1
|
||||
|
||||
|
||||
12819 cards in total.
|
||||
|
||||
|
||||
-------------
|
||||
Release Notes
|
||||
-------------
|
||||
|
||||
- Java 7 -
|
||||
The devs are discussing a plan to update the battlefield display code with Java FX 2.2 and this should help to improve a few things. Java FX 2.2 requires Java 7 so please update your Java runtime environment in the nearest future. At some point a new version of Forge will no longer run under Java 6.
|
||||
|
||||
|
||||
- New M14 cards -
|
||||
We have added a branch to our SVN for the new cards that are currently being scripted. These cards are not yet available in this build of forge. Please be patient and they will soon become available.
|
||||
|
||||
|
||||
- Match and Deck Editor Layout problems -
|
||||
The match and deck editor windows contain panels that can be moved and/or resized. The changes that you make are saved to files that are named "editor.xml" and "match.xml". These files can be found in your userDir/preferences/ directory.
|
||||
|
||||
Sometimes people will decide that they do not like the changes that they made and wish to go back to the original layout. To reset layouts to deafult, go to the Game Settings -> Preferences -> Troubleshooting section. You will find at this location two buttons that will reset the match layout and the deck editor layouts.
|
||||
|
||||
Also use the mentioned measure if your match or deckeditor won't start - it would help in 90% of the cases.
|
||||
|
||||
|
||||
- The AI Drafting has been improved -
|
||||
The AI evaluated the basic lands higher than anything else. Fixed. The AI would pick cards with RemAIDeck but only at a much lowered pick rate. For example the best pick in a 250 card set would become the 75th best pick, the 20th best pick would become the 95th and so on. Divided this factor by 3 (so the first pick would become the 25th pick). Please test whether this has improved the draft experience.
|
||||
|
||||
|
||||
---------
|
||||
New Cards
|
||||
---------
|
||||
|
||||
Amulet of Quoz
|
||||
Aphetto Dredging
|
||||
Archive Trap
|
||||
Battlefield Scrounger
|
||||
Chain Stasis
|
||||
Chancellor of the Annex
|
||||
Chisei, Heart of Oceans
|
||||
Choking Vines
|
||||
Cobra Trap
|
||||
Defensive Formation
|
||||
Dream Chisel
|
||||
Dream Leash
|
||||
Exiled Doomsayer
|
||||
Fossil Find
|
||||
Gemstone Caverns
|
||||
Grave Consequences
|
||||
Grimoire Thief
|
||||
Hankyu
|
||||
Hibernation's End
|
||||
Indentured Djinn
|
||||
Ion Storm
|
||||
Jester's Scepter
|
||||
Jetting Glasskite
|
||||
Jotun Grunt
|
||||
Kira, Great Glass Spinner
|
||||
Kithkin Armor
|
||||
Krark's Thumb
|
||||
Leashling
|
||||
Liquid Fire
|
||||
Martyr of Bones
|
||||
Master Warcraft
|
||||
Melee
|
||||
Minion of Leshrac
|
||||
Odric, Master Tactician
|
||||
Patron of the Akki
|
||||
Patron of the Kitsune
|
||||
Patron of the Moon
|
||||
Patron of the Nezumi
|
||||
Patron of the Orochi
|
||||
Penance
|
||||
Power Conduit
|
||||
Prowling Pangolin
|
||||
Psychic Vortex
|
||||
Research // Development
|
||||
Search for Survivors
|
||||
Shimmering Glasskite
|
||||
Spinning Darkness
|
||||
Summoning Trap
|
||||
Tainted Specter
|
||||
Teferi's Curse
|
||||
Temporary Truce
|
||||
Thelon's Chant
|
||||
Thought Lash
|
||||
Thran Turbine
|
||||
Tidal Influence
|
||||
Time and Tide
|
||||
Tourach's Chant
|
||||
Truce
|
||||
Uba Mask
|
||||
Void Maw
|
||||
|
||||
|
||||
----------
|
||||
New Planes
|
||||
----------
|
||||
|
||||
Furnace Layer
|
||||
Kharasha Foothills
|
||||
Mimano
|
||||
Mirrored Depths
|
||||
|
||||
|
||||
--------------------
|
||||
New M14 branch Cards
|
||||
--------------------
|
||||
|
||||
Accursed Spirit
|
||||
Advocate of the Beast
|
||||
Ajani's Chosen
|
||||
Archangel of Thune
|
||||
Awaken the Ancient
|
||||
Battle Sliver
|
||||
Blur Sliver
|
||||
Bonescythe Sliver
|
||||
Charging Grffin
|
||||
Corpse Hauler
|
||||
Dawnstrike Paladin
|
||||
Deathgaze Cockatrice
|
||||
Devout Invocation
|
||||
Elvish Mystic
|
||||
Enlarge
|
||||
Fleshpulper Giant
|
||||
Glimpse the Future
|
||||
Grim Return
|
||||
Groundshaker Sliver
|
||||
Guardian of the Ages
|
||||
Hive Stirrings
|
||||
Hunt the Weak
|
||||
Into the Wilds
|
||||
Jace's Mindseeker
|
||||
Kalonian Tusker
|
||||
Liliana's Reaver
|
||||
Marauding Maulhorn
|
||||
Master of Diversion
|
||||
Megantic Sliver
|
||||
Molten Birth
|
||||
Ogre Battledriver
|
||||
Predatory Sliver
|
||||
Primeval Bounty
|
||||
Regathan Firecat
|
||||
Ring of Three Wishes
|
||||
Rise of the Dark Realms
|
||||
Scourge of Valkas
|
||||
Sentinel Sliver
|
||||
Seraph of the Sword
|
||||
Shadowborn Apostle
|
||||
Shadowborn Demon
|
||||
Sliver Construct
|
||||
Soulmender
|
||||
Sporemound
|
||||
Staff of the Death Magus
|
||||
Staff of the Flame Magus
|
||||
Staff of the Mind Magus
|
||||
Staff of the Wild Magus
|
||||
Steelform Sliver
|
||||
Stonehorn Chanter
|
||||
Striking Sliver
|
||||
Thorncaster Sliver
|
||||
Undead Minotaur
|
||||
Vampire Warlord
|
||||
Vastwood Hydra
|
||||
Vial of Poison
|
||||
Windreader Sphinx
|
||||
Woodborn Behemoth
|
||||
Young Pyromancer
|
||||
|
||||
|
||||
------------
|
||||
Known Issues
|
||||
------------
|
||||
|
||||
A small number of cards including Wall of Corpses, Abu Ja'far and others with a similar ability are not functional. The common issue here is actually that they check the blocker/attacker after being removed from the battlefield. Probably need to use LKI. Regular destroy/destroy all effects work just fine. The problem cards are caring about battlefield specific properties, that no longer apply when the base card is no longer on the battlefield.
|
||||
|
||||
Several people have noticed that the cards displayed on the battlefield will fail to be displayed when the number of cards on the battlefield increases. Maximizing the human panel can help to re-display the cards.
|
||||
|
||||
Some time was spent turning the static ETB triggers into the proper ETB replacement effects they should be, mainly to interact correctly with each other. This work is not yet finished. As a result there is currently some inconsistencies with "Enters the battlefield with counters" (Not incredibly noticeable).
|
||||
|
||||
A recent contribution to the code base should fix some of the bugs that people noticed with cloning type abilities. At this time there is one remaining issue that we hope will be addressed in the near future:
|
||||
Copies of cards that setup Zone Change triggers via addComesIntoPlayCommand and addLeavesPlayCommand will not function correctly.
|
||||
|
||||
Forge is likely to be compatible with Java 7 at this time. Some people have used forge with Java 7 and have not reported any problems that are related to Java 7. If you would like to upgrade to Java 7 and have held off because of Forge then you may upgrade as we do not think that it will cause an incompatibility type of problem. We will continue to try to maintain compatibility with Java 6 for the foreseeable future.
|
||||
|
||||
The Forge archive includes a readme.txt file and we ask that you spend a few minutes reading this file as it contains some information that may prove useful. We do tend to update this file at times and you should quickly read this file and look for new information for each and every new release. Thank you.
|
||||
|
||||
The archive format used for the Forge distribution is ".tar.bz2". There are utilities for Windows, Mac OS and the various *nix's that can be used to extract/decompress these ".tar.bz2" archives. We recommend that you extract/decompress the Forge archive into a new and unused folder.
|
||||
|
||||
Some people use the Windows application 7zip. This utility can be found at http://www.7-zip.org/download.html. Mac users can double click on the archive and the application Archive Utility will launch and extract the archive. Mac users do not need to download a separate utility.
|
||||
|
||||
|
||||
----------------------------
|
||||
Contributors to This Release
|
||||
----------------------------
|
||||
|
||||
Agetian
|
||||
Asepetci
|
||||
Diogenes
|
||||
Gos
|
||||
Hellfish
|
||||
Marc
|
||||
Max
|
||||
Nordos
|
||||
RedDeckWins
|
||||
Sidereal
|
||||
Sloth
|
||||
Sol
|
||||
Swordshine
|
||||
Chris H
|
||||
|
||||
|
||||
(Quest icons used created by Teekatas, from his Legendora set http://raindropmemory.deviantart.com)
|
||||
(Thanks to the MAGE team for permission to use their targeting arrows.)
|
||||
(Thanks to http://www.freesound.org/browse/ for providing some sound files.)
|
||||
|
||||
|
||||
end
|
||||
700
README.txt
700
README.txt
@@ -1,700 +0,0 @@
|
||||
Installation Instructions:
|
||||
--------------------------
|
||||
|
||||
The archive format used for the Forge distribution is ".tar.bz2". There are utilities for Windows, Mac OS and the various *nix's that can be used to extract/decompress these ".tar.bz2" archives. We recommend that you extract/decompress the Forge archive into a new and unused folder.
|
||||
|
||||
Some people use the Windows application 7zip. This utility can be found at http://www.7-zip.org/download.html. Mac users can double click on the archive and the application Archive Utility will launch and extract the archive. Mac users do not need to download a separate utility.
|
||||
|
||||
Once the Forge archive has been decompressed you should then be able to launch Forge by using the included launcher. Launching Forge by double clicking on the forge jar file in the past caused a java heap space error. Forge's memory requirements have increased over time and the launchers increase the java heap space available to Forge. Currently you can launch Forge by double clicking on the forge jar file without a java heap space error but this is likely to change as we add in more sounds, icons, etc.
|
||||
|
||||
|
||||
Updating to a newer version Instructions:
|
||||
-----------------------------------------
|
||||
|
||||
- User data migration -
|
||||
User data files, like decks, saved gauntlets, and card pictures, are now stored in new directories separate from the program data. When this version of Forge is first run, it will scan the program directory for all user data and automatically migrate the files to their new homes. There are three defined user data directores: userDir, cacheDir, and cardPicsDir, and their locations depend on the standard paths for your operating system:
|
||||
|
||||
Windows:
|
||||
userDir=%APPDATA%/Forge/
|
||||
cacheDir=%LOCALAPPDATA%/Forge/Cache/ (or %APPDATA%/Forge/Cache/ for windows versions before the local/roaming directory split)
|
||||
OSX:
|
||||
userDir=$HOME/Library/Application Support/Forge/
|
||||
cacheDir=$HOME/Library/Caches/Forge/
|
||||
Linux:
|
||||
userDir=$HOME/.forge/
|
||||
cacheDir=$HOME/.cache/forge/
|
||||
|
||||
The appdata directory is hidden by default in Win7. Open a Windows Explorer window (or double-click on My Computer] and in the address field type "%appdata%/forge/" (without the quotes). If that doesn't work, type "%appdata"/roaming/forge".
|
||||
|
||||
cardPicsDir is defined as <cacheDir>/pics/cards/ by default. If you wish to use a non-default directory, please see the forge.profile.preferences.example file located in the Forge installation directory root. You can use this file to, for example, share the card pics directory with another program, such as Magic Workstation.
|
||||
|
||||
If you are using the Mac OS X version of Forge then you will find the forge.profile.preferences.example file by right clicking or control clicking on the Forge.app icon. Select "Show Package Contents" from the contextual menu. A Finder window will open and will display a folder named Contents. Navigate to the folder:
|
||||
/Contents/Resources/Java/
|
||||
and you will find the file named forge.profile.preferences.example.
|
||||
|
||||
For reference, here is the full list of moved directories and files:
|
||||
|
||||
Old location New location
|
||||
---------------- ----------------------
|
||||
res/decks/ <userDir>/decks/
|
||||
res/gauntlet/ <userDir>/gauntlet/
|
||||
res/layouts/ <userDir>/preferences/
|
||||
res/preferences/ <userDir>/preferences/
|
||||
res/quest/data/ <userDir>/quest/saves/
|
||||
res/pics/ <cacheDir>/pics/
|
||||
forge.log <userDir>/forge.log
|
||||
|
||||
- New Import Data dialog -
|
||||
The Import Pictures dialog, accessed via the Content Downloaders submenu, has received an overhaul and has been reincarnated as the Import Data dialog. You may recognize it from the automatic data migration procedure if you had any data to migrate when this version was first started. Instead of just importing pictures from a previous version of Forge, it can now import any kind of Forge data whatsoever. If you have a directory full of deck files, you can use the Import Data dialog to copy or move them to the appropriate directory. If you have just downloaded a torrent of high-quality pics for a particular set, use the Import Data dialog to get them to the proper place. The dialog give you a full listing of all file copy/move operations, so you can see what will happen before you click 'Start Import'.
|
||||
|
||||
An importer option was added for including pictures in set-specific card directories that don't map to any currently known card. This handles the case where people have collected complete sets of pics in anticipation of when Forge supports them.
|
||||
|
||||
|
||||
The Mac OS application version:
|
||||
-------------------------------
|
||||
|
||||
We have packaged the Forge BETA version as a Mac OS application. You can double click the Forge.app icon to launch the forge application on your Apple computer running Mac OS. This application will automatically increase the java heap space memory for you as it launches. This version does not require the forge.command file and it does not need to start the Terminal application as part of the start up process.
|
||||
|
||||
If you update your OS to Apple OSX 10.8 Mountain Lion and try to launch a new version of forge that you will likely get a dialog which states "File is damaged and cannot be opened. Please move to trash."
|
||||
|
||||
Mountain Lion comes with a new Gatekeeper feature and this is probably blocking your ability to launch this newer version of forge. Visit the link below and follow the instructions. They are fairly long and detailed.
|
||||
|
||||
http://support.apple.com/kb/HT5290?viewlocale=en_US&locale=en_US
|
||||
|
||||
Please note that the issue is most likely caused by Mountain Lion's Gatekeeper feature and it is extremely unlikely that the forge dev team will attempt to get a unique Developer ID from Apple and use it to digitally sign our forge app.
|
||||
|
||||
|
||||
Picture location info:
|
||||
----------------------
|
||||
|
||||
The instructions that were found in this section are now out of date. Current instructions can be found in the CHANGES.txt and forge.profile.properties.example files.
|
||||
|
||||
|
||||
Launching Forge and Memory Issues:
|
||||
----------------------------------
|
||||
|
||||
In the past, some people noticed java heap space errors and lengthy pauses. The memory requirements for Forge have increased over time. The default setting on your computer for the java heap space may not be enough to prevent the above problems.
|
||||
|
||||
The technically proficient can launch the forge jar with an argument from the CLI. The argument "-Xmx512m" may work for computers with 1 Gig of memory. Computers with 2 Gigs or more of memory should be able to use "-Xmx1024m" as an argument.
|
||||
|
||||
We have created several scripts that will launch the Forge jar with "-Xmx1024m" as an argument. People using Windows OS should double click the "forge.exe" file. People using one of the other *nix OS should double click the "forge.sh" file. People using Apple's Mac OS X should download the Mac version of Forge and then double click the "forge.app" application.
|
||||
|
||||
The script file must be located in the same folder as the Forge jar file and the Forge jar file name can not be changed. Otherwise, the scripts will not work.
|
||||
|
||||
If you have a low end machine you may find that the scripts above will prevent java heap space errors but will find that Forge still runs very slowly at times.
|
||||
|
||||
In this case you can try the following. You can try using low quality pictures rather than the high quality pictures. Or you can try removing all of the jpg pictures from the pics folder.
|
||||
|
||||
|
||||
Forge failed to launch:
|
||||
-----------------------
|
||||
|
||||
If you're trying to run Forge for the first time, but it doesn't open up, you can try the following to get some output and help yourself/us solve the problem.
|
||||
|
||||
1) Open up a terminal
|
||||
- Under Windows, press Windows+R, type "cmd", hit enter
|
||||
- Under Linux, you probably know that yourself. Use your distribution's application menu, and search for "terminal" in a group like "utilities".
|
||||
- Launch the program named "Console.app" which can be found in your /Applications/Utilities/ folder. Highlight the "All Messages" option and click on the "Clear Display" button before launching Forge.
|
||||
|
||||
2) Go to the folder where you unpacked Forge
|
||||
- Windows: Let's say your forge is in D:\Programs\Forge.
|
||||
- Type "D:", Enter to change to the D: drive.
|
||||
- Type "cd \Programs\Forge", Enter to change to the directory.
|
||||
- NOTE: On nonenglish systems, you might have problems due to the poor localization of Windows. Go to the innermost directory you find (worst case is "\"), then "dir", Enter to show all folders in that folder. Search for the one you're probably wanting. For Example the German "Programme" could really be "Program Files" or something like that.
|
||||
- NOTE: You might have to "quote" directory names with Spaces in them
|
||||
- Linux: Let's say your forge is in /home/user/Forge
|
||||
- Type "cd /home/user/Forge", Enter
|
||||
- NOTE: You might have to "quote" or 'quote' directory names with Spaces in them
|
||||
- Current versions of Forge no longer include a launcher script for Mac OS, proceed to step three.
|
||||
|
||||
3) Run Forge
|
||||
- On Windows, just type "forge.exe", Enter
|
||||
- On Linux, just type "forge.sh", Enter
|
||||
- Launch the Forge application bundle by double clicking on the program named "Forge.app".
|
||||
|
||||
Now you will probably see some sort of Error in the console. the first few lines contain a message that might help you. If you can't fix the problem yourself, please take the complete output and report your problem on the Forum.
|
||||
|
||||
|
||||
The Card Pictures disappear when you restart Forge:
|
||||
---------------------------------------------------
|
||||
|
||||
if you're running Windows 7, make sure you're running the program as an admin, otherwise no changes will be made to your system (nothing is saved). In Windows 7, Forge may be happier when run from somewhere in the My Documents structure, (they call them Libraries now???) or from another partition other than C:. The user has little permission to do much on the system drive.
|
||||
|
||||
|
||||
Java Issues:
|
||||
------------
|
||||
|
||||
Forge is likely to be compatible with Java 7 at this time. Some people have used forge with Java 7 and have not reported any problems that are related to Java 7. If you would like to upgrade to Java 7 and have held off because of Forge then you may upgrade as we do not think that it will cause an incompatibility type of problem. We will continue to try to maintain compatibility with Java 6 for the foreseeable future.
|
||||
|
||||
Forge requires Java 6 and will not run if you have an earlier version of Java. You will need to update to Java 6.
|
||||
|
||||
|
||||
Card Picture Issues:
|
||||
--------------------
|
||||
|
||||
The server which contained the high quality card pictures is now off line and these high quality card pictures are no longer available as a download from within the forge application. We apologize, but the current dev team do not maintain this server and this matter is out of our control.
|
||||
|
||||
Some people are choosing to re-download all of the low quality card and card set pictures when they install the next version of forge. This consumes large amounts of bandwidth needlessly. Please be careful!
|
||||
|
||||
|
||||
The instructions that were found in this section are now out of date. Current instructions can be found in the CHANGES.txt and forge.profile.properties.example files.
|
||||
|
||||
|
||||
When you install a new version of Forge, please follow the instructions that can be found in the CHANGES.txt and forge.profile.properties.example files. This will allow you to reuse your previous picture files and other user data files. This way you will only have to download the pictures for the new cards.
|
||||
|
||||
This should save enough bandwidth that everyone will be able to download the new set pictures from the cardforge server. We do appreciate your efforts to save bandwidth. Thank you.
|
||||
|
||||
|
||||
Reporting Bugs:
|
||||
---------------
|
||||
|
||||
To report a bug with an official beta release, please follow the instructions at http://www.slightlymagic.net/wiki/Forge#I_think_I_found_a_bug_in_Forge._What_do_I_do.3F .
|
||||
|
||||
To report a bug (1) with an alpha test, (2) with a nightly build, (3) with something compiled from the official Forge software repository, or (4) with the leading edge (formerly "SVN Bug Reports"), please do not submit your bugs to the forum. Instead, please follow the instructions at http://www.slightlymagic.net/wiki/How_to_File_a_Bug_Report_with_Mantis.
|
||||
|
||||
Forge will now allow you to upload a crash report to the Forge forum at CCGH.
|
||||
|
||||
|
||||
A new very hard tier category in Quest mode:
|
||||
--------------------------------------------
|
||||
|
||||
You will notice a new very hard tier category for the opponent. As you change from the previous tier to the next tier (easy to medium, etc.) you will now notice that there is not an abrupt change over. There is now a mixture of decks from the previous tier and the next tier for you to chose from. When you win a match you will complete the advancement to the next tier.
|
||||
|
||||
|
||||
The Forge Booster Draft mode:
|
||||
-----------------------------
|
||||
|
||||
A significant re-write of the Booster Draft functionality has taken place. Draft from the Full card pool, sets/blocks or custom drafts (like cube). The AI will pick cards more intelligently, and builds decks from picked cards. Old method would pick cards for deck and then stop picking new cards.
|
||||
|
||||
|
||||
The developer mode:
|
||||
-------------------
|
||||
|
||||
The developer mode gives us a few new features. These new features will primarily interest the devs as it will now help to test out specific cards while testing combat code changes. You can turn on or off this mode at the Home View -> Game Settings -> Preferences -> Gameplay Options section.
|
||||
|
||||
When turned on the battlefield will have a tab named "Dev Mode". There are a number of useful options in this tab. You can disable loss by milling, generate mana in your mana pool, tutor for card, etc.
|
||||
|
||||
|
||||
New foil card image available:
|
||||
------------------------------
|
||||
|
||||
Rob and Marc have worked together to add in a new feature which will overlay a card's image with a foil like film. A few random cards will have a foil like image in constructed mode games and possibly quest mode games. There is a check box on the Home View -> Game Settings -> Preferences view that you can use to turn this feature on or off.
|
||||
|
||||
|
||||
Informational icons overlays for cards on the battlefield:
|
||||
----------------------------------------------------------
|
||||
|
||||
The Battlefield will now display three symbolic icons that will overlay creature cards. These icons are used to denote summoning sickness and whether a creature is attacking or blocking. Added additional icons that will be drawn over the cards in the battlefield for phasing and counters at a later date. The attack/block/phasing icons and counters will now also be shown on large cards, only casting cost will be omitted. Lands and tokens with different amounts/types of counters will no longer stack. Tokens with different summoning sickness will no longer stack. Lands that become creatures will now always be moved to the front row.
|
||||
|
||||
|
||||
Optional choice for abilities that are on the stack:
|
||||
----------------------------------------------------
|
||||
|
||||
When a spell or an ability appears on the stack and it says "(OPTIONAL)" you can right-click it to decide if you want to always accept or to decline it.
|
||||
|
||||
|
||||
Multiple quest files:
|
||||
---------------------
|
||||
|
||||
Multiple quest files are now supported. This allows you to start a new quest and give it a unique name, and it will not overwrite your previous quest game file.
|
||||
|
||||
|
||||
The new UI now uses tabbed panes:
|
||||
---------------------------------
|
||||
|
||||
We now have a tab system for sub-menus in the home screen. Quest mode refactored to fit this tab system. It's now considerably easier to use - less cramped, less clicks, more functionality.
|
||||
|
||||
|
||||
The quest mode card shop:
|
||||
-------------------------
|
||||
|
||||
You can now buy PreCon decks, Starter packs, Tournament packs and Fat packs from the quest mode card shop.
|
||||
|
||||
|
||||
Player Avatar pictures:
|
||||
-----------------------
|
||||
|
||||
The UI has a few new features including the option to pick an avatar from a collection of pictures. This can be accessed from the Settings -> Avatars tab.
|
||||
|
||||
|
||||
The organizational structure of the /res/decks/ folder:
|
||||
-------------------------------------------------------
|
||||
|
||||
The organizational structure of the /res/decks/ folder has been improved and we now have these six subdirectories:
|
||||
|
||||
/decks/constructed/
|
||||
/decks/cube/
|
||||
/decks/draft/
|
||||
/decks/plane/
|
||||
/decks/scheme/
|
||||
/decks/sealed/
|
||||
|
||||
You can place your deck files from an earlier version of Forge into the /res/decks/ folder. When you next launch Forge these decks will be converted to a newer format and will be moved into the proper subdirectory.
|
||||
|
||||
Please not that your /decks/ directory no longer resides in you /res/ directory and has been moved to <userDir>/decks/.
|
||||
|
||||
|
||||
User-created themes for Forge's background, fonts, colors and icons:
|
||||
--------------------------------------------------------------------
|
||||
|
||||
When you select a new skin in the Preferences view Forge should save the change to the preference file, quit and then automatically re-launch with the new skin displayed. During testing some people have noticed that Forge is not restarting on their computer and they have to re-launch Forge themselves.
|
||||
|
||||
If anyone is interested in creating additional themes for inclusion in the Forge project then you should visit this topic at CCGH:
|
||||
|
||||
http://www.slightlymagic.net/forum/viewtopic.php?f=26&t=8449
|
||||
|
||||
|
||||
The Battlefield UI:
|
||||
-------------------
|
||||
|
||||
The Battlefield UI has a new feature implemented which allows us to move and resize the panels to new places on the battlefield. This allows us to personalize the battlefield display to our own liking. You should try moving panels by clicking and dragging their tabs.
|
||||
|
||||
If you do not like your efforts to personalize the battlefield display you can revert the display to the default layout by clicking on the Dock button labeled "Revert layout".
|
||||
|
||||
|
||||
The pets in quest mode:
|
||||
-----------------------
|
||||
|
||||
Some adjustments to the pets in quest mode were made. The quest mode plant wall's Deathtouch ability was deemed to be too strong against the AI's attack code and this ability was changed to Wither in this version. This includes a new pet.
|
||||
|
||||
|
||||
The dock tab has a new button labeled "Open Layout":
|
||||
----------------------------------------------------
|
||||
|
||||
The dock now has a new button labeled "Open Layout" along with old button with original function "Revert Layout". Modifying the battlefield layout will result in your changes being saved to a file named "match_preferred.xml". You can copy and rename that file to share your layouts with other people.
|
||||
|
||||
|
||||
The new Deck Editors:
|
||||
---------------------
|
||||
|
||||
The work on the new UI is now finished and this version adds the new UI to the deck editors. We all would like to thank Doublestrike for his contributions to the new UI.
|
||||
|
||||
The new deck editors include:
|
||||
|
||||
* a better text search (can search for multiple terms, and "not" terms
|
||||
* interval filters for P/T and CMC
|
||||
* add/remove 4
|
||||
* better statistics and draw probabilities
|
||||
* Toggle-able, sort-able, resize-able, move-able columns
|
||||
* and of course uses the drag cell layout.
|
||||
|
||||
|
||||
Performance issues on low end machines:
|
||||
---------------------------------------
|
||||
|
||||
Several people have noticed forge slowing down after playing a number of matches without quitting forge in between the matches that are played. The new UI may be involved somehow. We also hope to have this figured out and fixed in the near future. Please be patient in the meanwhile. A recent fix was implemented that should improve the slowdown problem somewhat.
|
||||
|
||||
A lot of time and effort have gone into fixing the memory leak problems that were recently noticed and reported to the dev team. Doublestrike and Slapshot deserve our applause and we are very thankful. People should be able to now play long multi match sessions without noticing slow downs and pauses.
|
||||
|
||||
Some performance changes were made to Forge and it should now operate more quickly on low end machines. Mid to high level machines are unlikely to notice as much of a performance increase. We tried to hunt down all of the bugs that resulted from these changes but there may still be a bug or two in this beta release.
|
||||
|
||||
|
||||
A note about winning booster packs in quest mode:
|
||||
-------------------------------------------------
|
||||
|
||||
If you win a quest mode match, you get a booster pack for every 1 or 2 (default) Wins, depending on the difficulty level. If you lose and you are playing on easy mode, you get a booster pack every 1 (default) Loss.
|
||||
|
||||
|
||||
The new UI:
|
||||
-----------
|
||||
|
||||
The first step was to update the battlefield window. The second step was to update the New Game window (now named Home view). We got constructed mode and then quest modes working first. We got the draft and sealed modes working again afterwards.
|
||||
|
||||
The work on the new UI is now for the most part finished. We should not expect major changes or major additions to the UI. Future betas may include a few minor bug fixes to the UI. And we may also include a few minor tweaks.
|
||||
|
||||
|
||||
The new Alpha Strike button:
|
||||
----------------------------
|
||||
|
||||
A new Alpha Strike button was added to the dock. The Dock is one of the tabs availble in the battlefield view.
|
||||
|
||||
|
||||
Using Forge with the new Mac OS Mountain Lion:
|
||||
----------------------------------------------
|
||||
|
||||
If you update your OS to Apple OSX 10.8 Mountain Lion and try to launch a new version of forge that you will likely get a dialog which states "File is damaged and cannot be opened. Please move to trash."
|
||||
|
||||
Mountain Lion comes with a new Gatekeeper feature and this is probably blocking your ability to launch this newer version of forge. Visit the link below and follow the instructions. They are fairly long and detailed.
|
||||
|
||||
http://support.apple.com/kb/HT5290?viewlocale=en_US&locale=en_US
|
||||
|
||||
Please note that the issue is most likely caused by Mountain Lion's Gatekeeper feature and it is extremely unlikely that the forge dev team will attempt to get a unique Developer ID from Apple and use it to digitally sign our forge app.
|
||||
|
||||
|
||||
The Forge sealed deck mode:
|
||||
---------------------------
|
||||
|
||||
Sealed Deck mode has had a complete rewrite. Full cardpool, block and custom modes are supported. Custom sealed files in the res/sealed folder are exactly the same as custom draft files, except the file extension ".sealed".
|
||||
|
||||
A distinction may now be made between AI decks and Human decks, with the addition of a deck Metadata "PlayerType", which right now just helps by sorting human decks into the human combo box and AI decks into the AI combo box.
|
||||
|
||||
The Forge sealed deck mode has undergone significant changes. You can find these in the 1.2.14 beta and later versions. Instead of a single sealed deck match, you can now choose a 1-5 round gauntlet-style tournament where you will face increasingly difficult (probably) opponent decks. You can also choose to use starter packs instead of boosters in the block mode, choose to use 3-12 boosters instead of the default 6 in the full cardpool and custom (cube) modes, and so on.
|
||||
|
||||
Perhaps the most notable changes to the sealed deck mode are related to "fantasy blocks" and the greatly increased flexibility you have when you are building your own blocks.
|
||||
|
||||
|
||||
The new Gauntlet mode:
|
||||
----------------------
|
||||
|
||||
A new Gauntlet mode has been added. This mode gives you four options: Quick Gauntlet, Build A Gauntlet, Load Gauntlet and Gauntlet Contests. You can create a group of computer decks to play against by choosing either Custom user decks, Quest Decks, Fully random color decks or Semi-random theme decks.
|
||||
|
||||
|
||||
The new Variant mode (was named Multiplayer):
|
||||
---------------------------------------------
|
||||
|
||||
A new multiplayer mode has also been added. You should be able to play against multiple AI opponents at this time. You should note that the current Archenemy mode does not use Schemes at this time.
|
||||
|
||||
A lot of things are planned for this new multiplayer mode and it will take time to finish. Please enjoy what we have at this time and be patient. :)
|
||||
|
||||
Since Multiplayer is so new, not all cards will be 100% compatible right away as we expand scripting to handle multiple players.
|
||||
|
||||
The older match layout files are incompatible with the new multiplayer mode. The original match_default.xml, match_preferred.xml and the match_preferred.xml saved to a different name files have to go and can no longer be used. You can keep your editor_preferred.xml file. But you will have to setup your match view panels using the new match_default.xml file.
|
||||
|
||||
|
||||
The new damage dialog:
|
||||
----------------------
|
||||
|
||||
The new damage dialog now uses the new UI.
|
||||
|
||||
|
||||
When choosing cards, sources, etc. using a list box:
|
||||
----------------------------------------------------
|
||||
|
||||
When choosing cards, sources, etc. using a list box, the currently selected card will now be visually highlighted on the play field (to better distinguish between e.g. three different cards with the same name on the play field). Now the visual highlighting of a card will also work when declaring the order of blockers.
|
||||
|
||||
|
||||
Return to Ravnica Guild Sealed Deck mode:
|
||||
-----------------------------------------
|
||||
|
||||
Added Return to Ravnica Guild Sealed Deck mode. Start a new sealed deck game, choose "Block / Set" and then scroll down until you find "Return to Ravnica Guild Sealed (block)". Select that. From the "Choose Set Combination" menu, select the first option. You will be prompted twice to pick your guild (once for the promo cards, once for the actual booster - you should choose the same guild both times). After that you're ready to go.
|
||||
|
||||
|
||||
Targeting arrows are now available in the battlefield display:
|
||||
--------------------------------------------------------------
|
||||
|
||||
The Targeting Overlay has been fixed and re-enabled. It now correctly shows the targeting arcs in cases when it previously showed them in the wrong direction. The match UI is properly refreshed when the targeting arcs are switched on/off. The defunct "mouseover-only" mode is currently disabled (it crashes Forge, difficult to fix).
|
||||
|
||||
The visuals for targeting arrows has been improved and looks better, with an adaptation of the arrow drawing code from MAGE. Thanks to the MAGE team for permission for the adaptation.
|
||||
|
||||
Some people have noticed slowdowns when Targeting arrows are enabled. The battlefield Dock tab includes a targeting icon. You can set the targeting arrows to Off or to Card mouseover to speed up the game.
|
||||
|
||||
|
||||
The new sound system:
|
||||
---------------------
|
||||
|
||||
Forge now has a sound effect system in place. Several basic sounds are linked to the code now and will be enabled when "Enable Sounds" option is checked in the preferences. It supports WAV and AU file formats.
|
||||
|
||||
Currently supported sound effects are:
|
||||
(the ones already available in the standard installation of Forge are marked with a [*])
|
||||
|
||||
AddCounter [*] - add_counter.wav - triggered when a counter is added to a permanent.
|
||||
Artifact[*] - artifact.wav - triggered when an artifact is played.
|
||||
ArtifactCreature [*] - artifact_creature.wav - triggered when an artifact creature is played.
|
||||
BlackLand [*] - black_land.wav - triggered when a land with the "B" mana ability is played.
|
||||
Block [*] - block.wav - triggered when a blocker is assigned.
|
||||
BlueLand [*] - blue_land.wav - triggered when a land with the "U" mana ability is played.
|
||||
Creature [*] - creature.wav - triggered when a creature is played.
|
||||
Damage - damage.wav - triggered when a creature is damaged.
|
||||
Destroy [*] - destroy.wav - triggered when a permanent is destroyed.
|
||||
Discard [*] - discard.wav - triggered when a player discards a card.
|
||||
Draw [*] - draw.wav - triggered when a player draws a card.
|
||||
Enchantment [*] - enchant.wav - triggered when an enchantment is played.
|
||||
EndOfTurn [*] - end_of_turn.wav - triggered at the end of turn.
|
||||
Equip [*] - equip.wav - triggered when an equipment is equipped.
|
||||
FlipCoin [*] - flip_coin.wav - triggered when a coin is flipped.
|
||||
GreenLand [*] - green_land.wav - triggered when a land with the "G" mana ability is played.
|
||||
Instant [*] - instant.wav - triggered when an instant is played.
|
||||
LifeLoss [*] - life_loss.wav - triggered when a player loses life.
|
||||
LoseDuel[*] - lose_duel.wav - triggered when a player loses a duel.
|
||||
ManaBurn - mana_burn.wav - triggered during a mana burn if the appropriate rule is enabled.
|
||||
OtherLand - other_land.wav - triggered when a land with non-color mana abilities or any other land is played.
|
||||
Planeswalker [*] - planeswalker.wav - triggered when a planeswalker is played.
|
||||
Poison [*] - poison.wav - triggered when a player receives a poison counter.
|
||||
RedLand [*] - red_land.wav - triggered when a land with the "R" mana ability is played.
|
||||
Regen - regeneration.wav - triggered when a creature is regenerated.
|
||||
RemoveCounter - remove_counter.wav - triggered when a counter is removed from a permanent.
|
||||
Sacrifice - sacrifice.wav - triggered when a permanent is sacrificed.
|
||||
Sorcery [*] - sorcery.wav - triggered when a sorcery is played.
|
||||
Shuffle [*] - shuffle.wav - triggered when a player shuffles his deck.
|
||||
Tap [*] - tap.wav - triggered when a permanent is tapped.
|
||||
Token [*] - token.wav - triggered when a token is created.
|
||||
Untap [*] - untap.wav - triggered when a permanent is untapped.
|
||||
WhiteLand [*] - white_land.wav - triggered when a land with the "W" mana ability is played.
|
||||
WinDuel [*] - win_duel.wav - triggered when a player wins the duel.
|
||||
|
||||
All sounds use the event bus model now and are not called directly.
|
||||
|
||||
|
||||
The new Vanguard mode:
|
||||
----------------------
|
||||
|
||||
We now have a Vanguard mode implemented. This is a work in progress. The older match layout files are incompatible with the new Vanguard mode. The original match_default.xml, match_preferred.xml and the match_preferred.xml saved to a different name files need to be deleted and can no longer be used. You can keep your editor_preferred.xml file. But you will have to setup your match view panels using the new match_default.xml file.
|
||||
|
||||
|
||||
The new Archenemy mode:
|
||||
-----------------------
|
||||
|
||||
Schemes have been added to the Archenemy mode. This is a work in progress and there may be a bug or two for us to find.
|
||||
|
||||
|
||||
Quest Worlds:
|
||||
-------------
|
||||
|
||||
This version allows you to travel between the regular quest world and the other worlds (Shandalar, Ravnica, Jamuraa, more may be added in the future) to get different duel opponents and challenges. You will have to complete your current challenges before traveling or you will lose them.
|
||||
|
||||
World-specific format enforcing and starting world selection are available. Something has to be done about locked (non-repeatable) challenges so they do not end up locking other challenges in different worlds.
|
||||
|
||||
|
||||
Forge now has sideboards for the human player:
|
||||
----------------------------------------------
|
||||
|
||||
Sideboards have been implemented for Human players. We currently have:
|
||||
|
||||
* Sideboard creation support in relevant deck editor modes.
|
||||
* In-game sideboarding with persistence between rounds in a match and sorting of cards in the in-game sideboard editor.
|
||||
* Sideboard supported as a zone, with some relevant cards already in.
|
||||
* Correct validation of decks, both before the game starts and between the rounds (Limited min 40, Constructed min 60, free-form sideboard/main in Draft and Sealed, 1:1 sideboarding with 0 or 15 cards allowed in sideboard in Constructed (all variants) and Quest; OK to have less than minimum between rounds in a match in all modes if lost cards on ante).
|
||||
* Correct (fingers crossed) interaction of sideboarding with other relevant aspects of Forge rule enforcement (mulligan and ante interactions were corrected, initial hand and library between rounds were both corrected, everything else looks so far so good).
|
||||
|
||||
We don't yet have:
|
||||
|
||||
* AI sideboarding.
|
||||
|
||||
|
||||
The deck conformance/legality limitation:
|
||||
-----------------------------------------
|
||||
|
||||
The deck conformance/legality is now a user-toggable preference and is enabled by default. You no longer need to turn on dev mode to play an illegal deck.
|
||||
|
||||
|
||||
Using Forge on displays that are only 600 pixels tall or slightly larger:
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
The "Sanctioned Format: Constructed" view should now be compatible with displays that are only 600 pixels tall. The deck list at 600 pixels tall should now display three lines of text rather than less than a single line of text.
|
||||
|
||||
|
||||
We are looking for help in finding additional sound files for the new sound feature:
|
||||
------------------------------------------------------------------------------------
|
||||
|
||||
This version of forge includes a few sound files for the new sound effect system. While we have several sounds assigned to a few of the available events there are a number of events that do not yet have a assigned sound file. This should be considered a work in progress and we could use some help in finding interesting sounds that we can add to forge.
|
||||
|
||||
The sound files need to be in wav or au format, wav appears to be more widespread but the code can handle either format. The sound files need to be copyright-free and they should be in the public domain.
|
||||
|
||||
You can either record your own sounds if you have the necessary equipment or you may be able to find an appropriate sound on a website such as http://www.freesound.org/browse/
|
||||
|
||||
You should note that sound files can be large and we would like to avoid this if possible. A good size to shoot for would be 50 K or less. There is a freeware sound editor that may have versions for all operating systems. This app is named Audacity.
|
||||
|
||||
We have a forge forum topic at the Collectible Card Games Headquarters web site that is devoted to finding sounds for this new sound system. Please visit this topic and contribute a sound or two. We can use your help and assistance. :)
|
||||
|
||||
http://www.slightlymagic.net/forum/viewtopic.php?f=26&t=8570
|
||||
|
||||
|
||||
Notes about the second Quest World, Jamuraa:
|
||||
--------------------------------------------
|
||||
|
||||
A second Quest World, Jamuraa, has been added to Forge. When playing Quest mode, it is now possible to 'Travel' between the regular Quest environment and the two Worlds, Shandalar and Jamuraa, both of which include special formats, opponents and challenges. Or you can start a new Quest in any of them.
|
||||
|
||||
Like Shandalar, Jamuraa is a fantasy world. Its peaceful existence has recently been wrecked by a planar conjunction that shattered the barriers between Jamuraa and the infernal planes known as Jahim, Saqar, and Jahannam. The demon planeswalkers who rule those planes, and their hellish sister, Lilith, are now extending their influence over Jamuraa and gradually destroying the whole continent. Your task is to fight their minions and ultimately challenge the four demons - but beware, their destructive might is unfathomable!
|
||||
|
||||
From a technical perspective, the following sets are available to the player in Jamuraa:
|
||||
5th Edition, Arabian Nights, Mirage, Visions, Weatherlight.
|
||||
|
||||
Jamuraa contains:
|
||||
- 81 opponent decks, broken down as follows: 13 'easy' decks, 17 'medium' decks, 31 'hard' decks, and 20 'very hard' decks.
|
||||
- 9 challenges, including the 4 demon planeswalkers (the 3 demon rulers and Lilith) and 5 other special scenarios set in Jamuraa. All challenges are repeatable. All are fairly hard, and the 4 demon challenges are especially fiendish.
|
||||
For the most part, the opponent duel and challenge decks are built with the same format restrictions as your own cardpool, and some of the easiest opponent decks were in fact based on a limited cardpool. But there will be exceptions, especially in the hard/very hard decks and challenges, which can be more like Vintage/T1 decks than pure Mirage + 5th Edition decks. There will be older cards here and there, and maybe even a random Tempest card or two (although these are extremely scarce!).
|
||||
Hint: if you find the later 'Vintage' opponent decks unfair or near-impossible to beat with your 5th Edition/Mirage block cards, you can Travel to Shandalar and collect some old power cards there, and then return to Jamuraa. Just remember to complete your challenges before traveling.
|
||||
|
||||
Information on the quest worlds format can be found in this topic:
|
||||
|
||||
http://www.slightlymagic.net/forum/viewtopic.php?f=26&t=9258
|
||||
|
||||
|
||||
New Deck Editor features with improved Filters:
|
||||
-----------------------------------------------
|
||||
|
||||
Some work is being done on the various deck editors -- including the quest spell shop -- and we hope to add in additional features while improving the UI. Here is a quick tutorial on the new features:
|
||||
|
||||
FILTERS
|
||||
The Filters tab has been removed and filters are now controlled from the Card Catalog tab. Pretty much everything that used to be a checkbox is now represented by a toggle button. The statistics labels at the top for colors and card types can now be clicked to toggle display of the related cards. Clicking the Total Cards label in the upper left will alternately set all filters on and off.
|
||||
|
||||
Text searches are done by just typing the the text box. The results will update as you type. Use the combo box to the right of the text box to set whether the search should be for cards with or without the specified text. The three toggle buttons to the right of the combo box allow you to specify where in the card the text should (or should not) match. Complex text searches, such as for goblin creatures with haste but not with kicker costs, are possible by stacking the search filters. Put the current search filter on the stack by selecting the "Add filter" button to the left of the text box and choosing the "Current text search" option. You can also use the keyboard shortcut Ctrl+Enter (Cmd+Enter on OSX). This will keep your current text search applied, but will clear the text box and allow you to input the next search filter. To perform the example goblin search above, you would:
|
||||
1) Ensure "in" is displayed in the the combo box. Enter "goblin" in the text box and deselect Name and Text so that only Type is selected.
|
||||
2) Hit Ctrl+Enter (Cmd+Enter on OSX). Notice the "Contains: 'goblin" in: type" filter appears below the text box.
|
||||
3) Type "haste" in the text box. Deselect Type and select Text. Hit Ctrl+Enter.
|
||||
4) Change the combo box from "in" to "not in". Type "kicker" in the text box.
|
||||
The shown entries match your combined search filter. Click the little 'x' in the upper right corner of each search widget to remove that filter from the filter stack.
|
||||
|
||||
Format filters allow you to restrict the shown cards to those that are legal in a particular format. Select the "Add filter" button, hover over the "Format" submenu, and choose one of the defined formats. The filter will appear below the text box and will be combined with whatever other filters you have on the stack. Hover the mouse over the filter widget to see details on allowed sets and banned cards. Keep in mind that cards from other, non-listed sets may still appear in the results if they are just reprints of allowed cards.
|
||||
|
||||
Set filters are similar to format filters except that a window will come up with a grid of checkboxes so you can select exactly which sets you would like shown. There is a checkbox at the bottom (off by default) that will allow the filter to include cards reprinted in unselected sets, just like the format filter above. If you don't check this checkbox, only cards printed in the selected sets will be shown, allowing you to focus on just that set's version of a particular card. This is very useful in quest mode when determining which cards in a set you have yet to collect.
|
||||
|
||||
Value range filters allow you to restrict the cards by power, toughness, and/or converted mana cost (CMC). The text boxes that appear in the filter widget are editable and update the shown cards in realtime as you modify the numbers.
|
||||
|
||||
Quest World filters are similar to Format filters in that they restrict the shown cards to a group of sets, respecting lists of banned cards. They are useful when constructing decks that will be valid in the various quest worlds. You can have more than one quest world filter active at the same time; useful if you are constructing a deck that will be used in multiple worlds.
|
||||
|
||||
SPELL SHOP
|
||||
The spell shop interface has also received some usability enhancements. The first one you may notice is the addition of a new column named "Owned". This column is intended to help players decide whether buying an item is likely to be beneficial. The data in this column varies depending on what kind of item the row represents:
|
||||
Cards: A number indicating how many of a particular card a player already owns
|
||||
Preconstructed decks: A "YES" or "NO" indicating whether the deck exists in the player's deck list
|
||||
Booster/fat packs: A percentage indicating how close a player is to completing the related set. "Complete" means at least one of every basic land and at least 4 of every other card.
|
||||
If you don't want this column, it can be turned off in the editor preferences.
|
||||
|
||||
The new "Sell excess cards" button appears above the player's library. Clicking it will sell all cards that are not basic lands until only four copies of the cards remain. It's a one-click "cleanup" of the library and a great way to safely and quickly regain some cash.
|
||||
|
||||
The "Full catalog view" button appears to the left of the "Buy Card" button. Toggling this button will switch between showing the store's inventory and the full catalog. By applying a filter to show only a particular set (or group of sets), players can use this view to discover exactly which cards they do not own. Buying and selling cards is disabled while in this view.
|
||||
|
||||
Multibuy: By selecting any number of items and hitting space (or selecting the "Buy Card" or "Sell Card" buttons), a player can buy one of everything selected.
|
||||
|
||||
Find-as-you-type is now implemented for Deck Editor tables. Just start typing while the table has focus and the next card with a matching string in its name will be highlighted. If more than one card matches, hit Enter to select the next matching card. A popup panel will appear with the search string so you know what you are searching for. If no cards match the string, the string will be highlighted in red. Normally, if you hit the spacebar while a card is selected in one table, the card will be moved to the other table (catalog/deck). When the popup is displayed, space characters are interpreted as part of the search string. Find-as-you-type mode is automatically exited after 5 seconds of inactivity, or hit Escape to exit find-as-you-type mode immediately.
|
||||
|
||||
The Deck Editor has also gained some hotkey and context menu abilities. R-click on a card (or a group of selected cards) for a list of actions and keyboard shortcuts. In particular, you can now transfer cards 4 at a time using the keyboard and interact with the sideboard from anywhere. Also remember that you can jump to the other table with the arrow keys and jump to the text filter with ctrl/cmd+f. From the text filter, you can jump down to the tables by pressing enter.
|
||||
|
||||
|
||||
The Game Log:
|
||||
-------------
|
||||
|
||||
Added a 'copy to clipboard' button on WinLose screen so players can easily copy the game log.
|
||||
|
||||
|
||||
The UI is more keyboard-friendly:
|
||||
---------------------------------
|
||||
|
||||
Work was also done on making the UI more keyboard-friendly. For example, the OK button should now stay focused during matches, so you can advance through the stages by hitting Enter without having to go over and click the button all the time. If you find the button is losing focus, please report it as a bug.
|
||||
|
||||
|
||||
Gatecrash Guild Sealed game mode:
|
||||
---------------------------------
|
||||
|
||||
Gatecrash Guild Sealed game mode has been added. To use it, start a new Sealed Mode Game, select "Block / Set" and "Gatecrash Guild Sealed". Select the first (default) configuration in the "Choose Set Combination" dialog, and when asked to pick your boosters, choose the guild you want twice (once for the guild-specific booster, and then for the extra promo cards).
|
||||
|
||||
The following cards are not included in the guild boosters of this game mode because they are not currently implemented in Forge: Bane Alley Broker, Bioshift, Killing Glare, Simic Manipulator.
|
||||
|
||||
|
||||
New User Preferences:
|
||||
---------------------
|
||||
The display of clones and copied cards is now a matter of user preference. A user to now choose whether a clone/copied card should use its native artwork or the artwork of the card being cloned. So if Infinite Reflection is played and the "Clones use original card art" preference is checked, the cards that become copies of the enchanted card will still use their own art whereas by default they would all use the card art from the enchanted card.
|
||||
|
||||
|
||||
Flippable Cards:
|
||||
----------------
|
||||
|
||||
Flippable cards -- cards that have an alternate card printed upside-down at the bottom -- will now flip their appearance on the battlefield when in a flipped state. They are now also flippable in the card viewer by clicking on the card image, just like double-sided "transform" cards have been. When flipped this way, the details of the flipped side can be examined in the Card Details oracle text.
|
||||
|
||||
|
||||
High Quality Booster Pictures:
|
||||
------------------------------
|
||||
|
||||
Forge will now download high quality booster pictures. If you are interested in these high quality booster pictures, then you should delete the pictures that are found in your <cacheDir>/pics/boosters/ directory. Then download the new pictures by using the Home View -> Game Settings -> Content Downloaders -> Download Quest Images button.
|
||||
|
||||
|
||||
Flip, Transform and Morph cards:
|
||||
--------------------------------
|
||||
|
||||
When you mouse over a flip, transform or Morph (controlled by you) card in battlefield, you may hold SHIFT to see other state of that card at the side panel that displays card picture and details.
|
||||
|
||||
|
||||
Alternate sound system:
|
||||
-----------------------
|
||||
|
||||
Implemented an alternative sound system for people who have issues with sound gradually or instantly disappearing on certain Linux systems. Can be switched on/off in the preferences without needing a restart. Uses the standard Java Sound API so it doesn't require an external dependency. It's non-caching and, as such, less efficient than the regular sound system, so only use it in case you have issues with the default system, otherwise leave the option unchecked.
|
||||
|
||||
|
||||
Human vs Human play over the internet:
|
||||
--------------------------------------
|
||||
|
||||
Some initial code has been added that will at some point in the future allow human vs human play over the internet. This is a work in progress and is far from being implemented at this time. Please be patient. Anyone who is curious can read the messages in the http://www.slightlymagic.net/forum/viewtopic.php?f=52&t=9837 topic.
|
||||
|
||||
|
||||
Random Deck generation:
|
||||
-----------------------
|
||||
|
||||
Deck generation is now strictly conforming the colors chosen. You won't get any Boros Reckoner in a RG deck, that could be added before the change (because its manacost could be paid with red mana). Avacyn's Piligrim won't be added in a deck that has green but doesn't have white, though it does not consume, but produces white mana. As well there won't be added any creatures whose activated abilities require colors not choosen for a given deck. That is to say that now color identity is used for deck generation, that allows a better filtering of cards.
|
||||
|
||||
|
||||
Single declare attackers step:
|
||||
------------------------------
|
||||
|
||||
Combined declare attackers step for all Defending Players/Planeswalkers. On declare attackers step you have to click the entiry you are about to attack and then click on the creatures that should attack it. Clicking on a planeswalker or player visually highlights it, so that you will see whos attackers are assigned at the moment. By default your first opponent is pre-selected.
|
||||
|
||||
|
||||
Booster slots:
|
||||
--------------
|
||||
|
||||
Booster slots are now way more customizable. This change allows us to implement DGM boosters correctly. Note that custom cube .draft and .sealed files have changed their format. They require a booster description instead of Num(Rarity) N lines. Find example records in files describing SkieraCube and Juzamjedi cude coming with Forge. Singleton parameter is obsolette, since all cards within a booster slot are unique. Ignore rarity is also deprecated, for Booster options allow you to pick cards regardless of rarity with 'any' word standing for ignored rarity. (ex: 15 Any)
|
||||
|
||||
|
||||
Quest challenge start in play cards:
|
||||
------------------------------------
|
||||
|
||||
We have received reports that the quest challenge start in play cards are not appearing on the battlefield for people who were playing the classic mode rather than the fantasy mode. This should now be fixed.
|
||||
|
||||
|
||||
Commander:
|
||||
----------
|
||||
|
||||
We are taking baby steps toward Commander but there are some hurdles left before we get there. We need to implement the replacement effect that moves the commander back to your command zone; the cost-changing depending on how many times you've cast your commander; generating colorless mana if you try to generate mana outside your commanders color identity and AI for all the above.
|
||||
|
||||
|
||||
Customize your Sealed Deck games with fantasy blocks:
|
||||
-----------------------------------------------------
|
||||
|
||||
We have an informative topic at CCGH which explains how you can create "fantasy blocks" and the greatly increased flexibility you have when you are building your own blocks. Please see the topic http://www.slightlymagic.net/forum/viewtopic.php?f=26&t=8164 for more information. Also note that the syntax was recently changed, see message http://www.slightlymagic.net/forum/viewtopic.php?f=26&t=8164&view=unread#p117389 for additional information.
|
||||
|
||||
|
||||
Custom Cube:
|
||||
------------
|
||||
|
||||
1. You can create and save your cube as a constructed deck. Place the .dck file in the /res/cube directory.
|
||||
2. To create a description file in the /res/draft folder you need to make a copy of any existing *.draft file and the adjust the deckfile and booster structure to meet your needs.
|
||||
|
||||
This file format is outdated. "NumCards" is no longer used. Instead there should be a line describing a booster.
|
||||
|
||||
Find some examples below:
|
||||
|
||||
Booster: 1 Rare, 11 Common, 3 Uncommon
|
||||
Booster: 5 Common, 5 Uncommon, 5 Rare, 1 Mythic
|
||||
Booster: 16 Any
|
||||
Booster: 10 Common, 3 Uncommon, 1 RareMythic, 1 BasicLand
|
||||
|
||||
|
||||
Blank screen when starting a match:
|
||||
-----------------------------------
|
||||
|
||||
This problem should be fixed. At least you'll see an exception/crash report describing why the match didn't start instead of a blank screen.
|
||||
|
||||
|
||||
Duplicate triggers problem solved:
|
||||
----------------------------------
|
||||
|
||||
This problem has now been fixed. (Mana Vortex required 2 lands, Emrakul gave 2 extra turns, Empty the Warrens creatures two Storm triggers instead of one.)
|
||||
|
||||
|
||||
Constructed mode AI vs AI matches:
|
||||
----------------------------------
|
||||
|
||||
Added support for AI vs AI matches. There is a checkbox which allows you to view the AI vs AI match. This is a work in progress and is not finished. While it appears to work OK there is still a need for a few UI changes. Please be patient. These AI vs AI matches will not be a good way to test out various deck types against one another as the AI does not understand card combos and the AI is still limited at this time.
|
||||
|
||||
|
||||
Constructed mode Human vs Human Hotseat matches:
|
||||
------------------------------------------------
|
||||
|
||||
Hotseat matches are now possible. This mode has some known bugs to fix. At this time both players must use the same computer. Human vs Human matches via the internet is planned and should become available sometime in the future. Please be patient.
|
||||
|
||||
|
||||
Using predetermined decks in quest mode Challenges:
|
||||
---------------------------------------------------
|
||||
|
||||
Added the capability to play Challenges vs predetermined decks (along with a few other related options to disallow specific quest mode things). Added Sorin vs Tibalt, and Tibalt vs Sorin as examples of Challenges that force you to use a specific Deck. (They seemed to be the best duel deck compatibility for the AI).
|
||||
|
||||
|
||||
Targeting Overlay:
|
||||
------------------
|
||||
|
||||
Targeting arrows will now be shown for equipments equipping permanents currently under opponent's control (for those rare cases when e.g. an equipped creature gets Switcheroo'd for something else).
|
||||
|
||||
|
||||
Our Lawyers Made Us Do This:
|
||||
----------------------------
|
||||
|
||||
This product includes software developed by the Indiana University Extreme! Lab (http://www.extreme.indiana.edu/).
|
||||
9
forge-ai/.classpath
Normal file
9
forge-ai/.classpath
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
forge-ai/.project
Normal file
23
forge-ai/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>forge-ai</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
4
forge-ai/.settings/org.eclipse.core.resources.prefs
Normal file
4
forge-ai/.settings/org.eclipse.core.resources.prefs
Normal file
@@ -0,0 +1,4 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=ISO-8859-1
|
||||
encoding//src/test/java=ISO-8859-1
|
||||
encoding/<project>=ISO-8859-1
|
||||
5
forge-ai/.settings/org.eclipse.jdt.core.prefs
Normal file
5
forge-ai/.settings/org.eclipse.jdt.core.prefs
Normal file
@@ -0,0 +1,5 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.source=1.7
|
||||
4
forge-ai/.settings/org.eclipse.m2e.core.prefs
Normal file
4
forge-ai/.settings/org.eclipse.m2e.core.prefs
Normal file
@@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
||||
28
forge-ai/pom.xml
Normal file
28
forge-ai/pom.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.5.13</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
<name>Forge AI</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>forge</groupId>
|
||||
<artifactId>forge-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>forge</groupId>
|
||||
<artifactId>forge-game</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -15,27 +15,27 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.game.ai;
|
||||
package forge.ai;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.Card;
|
||||
import forge.CardLists;
|
||||
import forge.CounterType;
|
||||
import forge.GameEntity;
|
||||
import forge.card.trigger.Trigger;
|
||||
import forge.card.trigger.TriggerType;
|
||||
import forge.game.Game;
|
||||
import forge.game.phase.Combat;
|
||||
import forge.game.phase.CombatUtil;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
|
||||
//doesHumanAttackAndWin() uses the global variable AllZone.getComputerPlayer()
|
||||
/**
|
||||
@@ -52,18 +52,18 @@ public class AiAttackController {
|
||||
private final List<Card> attackers;
|
||||
private final List<Card> blockers;
|
||||
|
||||
private final Random random = MyRandom.getRandom();
|
||||
private final int randomInt = this.random.nextInt();
|
||||
private final static Random random = new Random();
|
||||
private final static int randomInt = random.nextInt();
|
||||
|
||||
private List<Card> oppList; // holds human player creatures
|
||||
private List<Card> myList; // holds computer creatures
|
||||
|
||||
private final Player ai;
|
||||
private final Player opponent;
|
||||
private Player defendingOpponent;
|
||||
|
||||
private int aiAggression = 0; // added by Masher, how aggressive the ai is
|
||||
// attack will be depending on circumstances
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -75,23 +75,36 @@ public class AiAttackController {
|
||||
* @param possibleBlockers
|
||||
* a {@link forge.CardList} object.
|
||||
*/
|
||||
public AiAttackController(final Player ai, final Player opponent) {
|
||||
public AiAttackController(final Player ai) {
|
||||
this.ai = ai;
|
||||
this.opponent = opponent;
|
||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
||||
|
||||
this.oppList = opponent.getCreaturesInPlay();
|
||||
this.oppList = Lists.newArrayList();
|
||||
this.oppList.addAll(this.defendingOpponent.getCreaturesInPlay());
|
||||
this.myList = ai.getCreaturesInPlay();
|
||||
|
||||
|
||||
this.attackers = new ArrayList<Card>();
|
||||
for (Card c : myList) {
|
||||
if (CombatUtil.canAttack(c, opponent)) {
|
||||
if (CombatUtil.canAttack(c, this.defendingOpponent)) {
|
||||
attackers.add(c);
|
||||
}
|
||||
}
|
||||
this.blockers = this.getPossibleBlockers(oppList, this.attackers);
|
||||
} // constructor
|
||||
|
||||
/** Choose opponent for AI to attack here. Expand as necessary. */
|
||||
private Player choosePreferredDefenderPlayer() {
|
||||
Player defender = ai.getWeakestOpponent(); //Gets opponent with the least life
|
||||
|
||||
if (defender.getLife() < 8) { //Concentrate on opponent within easy kill range
|
||||
return defender;
|
||||
} else { //Otherwise choose a random opponent to ensure no ganging up on players
|
||||
defender = ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
|
||||
}
|
||||
return defender;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* sortAttackers.
|
||||
@@ -109,6 +122,7 @@ public class AiAttackController {
|
||||
for (final Trigger trigger : attacker.getTriggers()) {
|
||||
if (trigger.getMode() == TriggerType.Attacks) {
|
||||
list.add(attacker);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,9 +143,9 @@ public class AiAttackController {
|
||||
* </p>
|
||||
*
|
||||
* @param attacker
|
||||
* a {@link forge.Card} object.
|
||||
* a {@link forge.game.card.Card} object.
|
||||
* @param combat
|
||||
* a {@link forge.game.phase.Combat} object.
|
||||
* a {@link forge.game.combat.Combat} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
public final boolean isEffectiveAttacker(final Player ai, final Card attacker, final Combat combat) {
|
||||
@@ -141,14 +155,14 @@ public class AiAttackController {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = this.defendingOpponent;
|
||||
if (ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat) > 0) {
|
||||
return true;
|
||||
}
|
||||
if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) {
|
||||
return true;
|
||||
}
|
||||
if (this.attackers.size() == 1 && attacker.hasKeyword("Exalted")) {
|
||||
if (this.attackers.size() == 1 && attacker.hasKeyword("Exalted") && ComputerUtilCombat.predictDamageTo(opp, 1, attacker, true) > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -192,7 +206,7 @@ public class AiAttackController {
|
||||
* </p>
|
||||
*
|
||||
* @param c
|
||||
* a {@link forge.Card} object.
|
||||
* a {@link forge.game.card.Card} object.
|
||||
* @param attackers
|
||||
* a {@link forge.CardList} object.
|
||||
* @return a boolean.
|
||||
@@ -221,7 +235,7 @@ public class AiAttackController {
|
||||
* @param attackers
|
||||
* a {@link forge.CardList} object.
|
||||
* @param combat
|
||||
* a {@link forge.game.phase.Combat} object.
|
||||
* a {@link forge.game.combat.Combat} object.
|
||||
* @return a {@link forge.CardList} object.
|
||||
*/
|
||||
public final List<Card> notNeededAsBlockers(final Player ai, final List<Card> attackers) {
|
||||
@@ -277,7 +291,7 @@ public class AiAttackController {
|
||||
return notNeededAsBlockers;
|
||||
}
|
||||
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = this.defendingOpponent;
|
||||
|
||||
// Increase the total number of blockers needed by 1 if Finest Hour in
|
||||
// play
|
||||
@@ -343,7 +357,7 @@ public class AiAttackController {
|
||||
if (!CombatUtil.canAttackNextTurn(attacker)) {
|
||||
continue;
|
||||
}
|
||||
if (blockersLeft > 0 && CombatUtil.canBeBlocked(attacker)) {
|
||||
if (blockersLeft > 0 && CombatUtil.canBeBlocked(attacker, ai)) {
|
||||
blockersLeft--;
|
||||
continue;
|
||||
}
|
||||
@@ -385,19 +399,19 @@ public class AiAttackController {
|
||||
final List<Card> unblockedAttackers = new ArrayList<Card>();
|
||||
final List<Card> remainingAttackers = new ArrayList<Card>(this.attackers);
|
||||
final List<Card> remainingBlockers = new ArrayList<Card>(this.blockers);
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = this.defendingOpponent;
|
||||
|
||||
|
||||
for (int i = 0; i < this.attackers.size(); i++) {
|
||||
if (!CombatUtil.canBeBlocked(this.attackers.get(i), this.blockers)
|
||||
|| this.attackers.get(i).hasKeyword("You may have CARDNAME assign its combat damage as though"
|
||||
+ " it weren't blocked.")) {
|
||||
unblockedAttackers.add(this.attackers.get(i));
|
||||
for (Card attacker : attackers) {
|
||||
if (!CombatUtil.canBeBlocked(attacker, this.blockers, null)
|
||||
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
||||
unblockedAttackers.add(attacker);
|
||||
}
|
||||
}
|
||||
|
||||
for (Card blocker : this.blockers) {
|
||||
if (blocker.hasKeyword("CARDNAME can block any number of creatures.")) {
|
||||
if (blocker.hasKeyword("CARDNAME can block any number of creatures.")
|
||||
|| blocker.hasKeyword("CARDNAME can block an additional ninety-nine creatures.")) {
|
||||
for (Card attacker : this.attackers) {
|
||||
if (CombatUtil.canBlock(attacker, blocker)) {
|
||||
remainingAttackers.remove(attacker);
|
||||
@@ -440,64 +454,78 @@ public class AiAttackController {
|
||||
* </p>
|
||||
*
|
||||
* @param c
|
||||
* a {@link forge.game.phase.Combat} object.
|
||||
* a {@link forge.game.combat.Combat} object.
|
||||
* @param bAssault
|
||||
* a boolean.
|
||||
*/
|
||||
public final GameEntity chooseDefender(final Combat c, final Combat gameCombat, final boolean bAssault) {
|
||||
private final GameEntity chooseDefender(final Combat c, final boolean bAssault) {
|
||||
final List<GameEntity> defs = c.getDefenders();
|
||||
|
||||
// Start with last planeswalker
|
||||
int n = defs.size() - 1;
|
||||
if (defs.size() == 1) {
|
||||
return defs.get(0);
|
||||
}
|
||||
Player prefDefender = (Player) (defs.contains(this.defendingOpponent) ? this.defendingOpponent : defs.get(0));
|
||||
|
||||
final GameEntity entity = ai.getMustAttackEntity();
|
||||
if (null != entity) {
|
||||
final List<GameEntity> defenders = gameCombat.getDefenders();
|
||||
n = defenders.indexOf(entity);
|
||||
int n = defs.indexOf(entity);
|
||||
if (-1 == n) {
|
||||
System.out.println("getMustAttackEntity() returned something not in defenders.");
|
||||
return defs.get(0);
|
||||
return prefDefender;
|
||||
} else {
|
||||
return entity;
|
||||
}
|
||||
} else {
|
||||
return defs.get(bAssault ? 0 : n);
|
||||
// 1. assault the opponent if you can kill him
|
||||
if (bAssault) {
|
||||
return prefDefender;
|
||||
}
|
||||
// 2. attack planeswalkers
|
||||
List<Card> pwDefending = c.getDefendingPlaneswalkers();
|
||||
if (!pwDefending.isEmpty()) {
|
||||
return pwDefending.get(0);
|
||||
} else {
|
||||
return prefDefender;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final boolean LOG_AI_ATTACKS = false;
|
||||
|
||||
|
||||
public final List<Player> getDefendingPlayers(Combat combat) {
|
||||
final List<Player> defending = new ArrayList<Player>();
|
||||
for (final GameEntity o : combat.getDefenders()) {
|
||||
if (o instanceof Player) {
|
||||
defending.add((Player) o);
|
||||
}
|
||||
}
|
||||
return defending;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>attackers</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link forge.game.phase.Combat} object.
|
||||
* @return a {@link forge.game.combat.Combat} object.
|
||||
*/
|
||||
public final Combat getAttackers() {
|
||||
public final void declareAttackers(final Combat combat) {
|
||||
// if this method is called multiple times during a turn,
|
||||
// it will always return the same value
|
||||
// randomInt is used so that the computer doesn't always
|
||||
// do the same thing on turn 3 if he had the same creatures in play
|
||||
// I know this is a little confusing
|
||||
Game game = ai.getGame();
|
||||
|
||||
this.random.setSeed(game.getPhaseHandler().getTurn() + this.randomInt);
|
||||
|
||||
final Combat combat = new Combat();
|
||||
combat.setAttackingPlayer(game.getCombat().getAttackingPlayer());
|
||||
|
||||
game.getCombat().initiatePossibleDefenders(opponent);
|
||||
combat.setDefenders(game.getCombat().getDefenders());
|
||||
|
||||
random.setSeed(ai.getGame().getPhaseHandler().getTurn() + AiAttackController.randomInt);
|
||||
|
||||
if (this.attackers.isEmpty()) {
|
||||
return combat;
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean bAssault = this.doAssault(ai);
|
||||
// Determine who will be attacked
|
||||
GameEntity defender = this.chooseDefender(combat, game.getCombat(), bAssault);
|
||||
GameEntity defender = this.chooseDefender(combat, bAssault);
|
||||
List<Card> attackersLeft = new ArrayList<Card>(this.attackers);
|
||||
// Attackers that don't really have a choice
|
||||
for (final Card attacker : this.attackers) {
|
||||
@@ -507,22 +535,26 @@ public class AiAttackController {
|
||||
boolean mustAttack = false;
|
||||
for (String s : attacker.getKeyword()) {
|
||||
if (s.equals("CARDNAME attacks each turn if able.")
|
||||
|| s.equals("At the beginning of the end step, destroy CARDNAME.")
|
||||
|| s.startsWith("CARDNAME attacks specific player each combat if able")) {
|
||||
mustAttack = true;
|
||||
break;
|
||||
}
|
||||
if ((s.equals("At the beginning of the end step, destroy CARDNAME.")
|
||||
|| s.equals("At the beginning of the end step, exile CARDNAME.")
|
||||
|| s.equals("At the beginning of the end step, sacrifice CARDNAME.")) {
|
||||
|| s.equals("At the beginning of the end step, sacrifice CARDNAME."))
|
||||
&& isEffectiveAttacker(ai, attacker, combat)) {
|
||||
mustAttack = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mustAttack || attacker.getSacrificeAtEOT()
|
||||
|| attacker.getController().getMustAttackEntity() != null
|
||||
if (mustAttack || attacker.getController().getMustAttackEntity() != null
|
||||
|| attacker.getSVar("MustAttack").equals("True")) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
attackersLeft.remove(attacker);
|
||||
}
|
||||
}
|
||||
if (attackersLeft.isEmpty()) {
|
||||
return combat;
|
||||
return;
|
||||
}
|
||||
if (bAssault) {
|
||||
if ( LOG_AI_ATTACKS )
|
||||
@@ -533,7 +565,7 @@ public class AiAttackController {
|
||||
combat.addAttacker(attacker, defender);
|
||||
}
|
||||
}
|
||||
return combat;
|
||||
return;
|
||||
}
|
||||
|
||||
// Exalted
|
||||
@@ -545,8 +577,7 @@ public class AiAttackController {
|
||||
exalted = true;
|
||||
break;
|
||||
}
|
||||
if (c.getName().equals("Finest Hour")
|
||||
&& game.getPhaseHandler().isFirstCombat()) {
|
||||
if (c.getName().equals("Finest Hour") && ai.getGame().getPhaseHandler().isFirstCombat()) {
|
||||
exalted = true;
|
||||
break;
|
||||
}
|
||||
@@ -566,7 +597,7 @@ public class AiAttackController {
|
||||
for (Card attacker : this.attackers) {
|
||||
if (CombatUtil.canAttack(attacker, defender, combat) && this.shouldAttack(ai, attacker, this.blockers, combat)) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
return combat;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -607,7 +638,7 @@ public class AiAttackController {
|
||||
aiLifeToPlayerDamageRatio = (double) ai.getLife() / candidateCounterAttackDamage;
|
||||
}
|
||||
|
||||
final Player opp = ai.getOpponent();
|
||||
final Player opp = this.defendingOpponent;
|
||||
// get the potential damage and strength of the AI forces
|
||||
final List<Card> candidateAttackers = new ArrayList<Card>();
|
||||
int candidateUnblockedDamage = 0;
|
||||
@@ -770,11 +801,13 @@ public class AiAttackController {
|
||||
attackersLeft = this.notNeededAsBlockers(ai, attackersLeft);
|
||||
attackersLeft = this.sortAttackers(attackersLeft);
|
||||
|
||||
int iDefender = combat.getDefenders().indexOf(defender);
|
||||
if ( LOG_AI_ATTACKS )
|
||||
System.out.println("attackersLeft = " + attackersLeft);
|
||||
|
||||
for (int i = 0; i < attackersLeft.size(); i++) {
|
||||
final Card attacker = attackersLeft.get(i);
|
||||
if (this.aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike()
|
||||
&& ComputerUtilCombat.getTotalFirstStrikeBlockPower(attacker, ai.getOpponent())
|
||||
&& ComputerUtilCombat.getTotalFirstStrikeBlockPower(attacker, this.defendingOpponent)
|
||||
>= ComputerUtilCombat.getDamageToKill(attacker)) {
|
||||
continue;
|
||||
}
|
||||
@@ -782,30 +815,39 @@ public class AiAttackController {
|
||||
if (this.shouldAttack(ai, attacker, this.blockers, combat) && CombatUtil.canAttack(attacker, defender, combat)) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
// check if attackers are enough to finish the attacked planeswalker
|
||||
if (iDefender > 0) {
|
||||
if (defender instanceof Card) {
|
||||
Card pw = (Card) defender;
|
||||
final int blockNum = this.blockers.size();
|
||||
int attackNum = 0;
|
||||
int damage = 0;
|
||||
List<Card> attacking = combat.getAttackersByDefenderSlot(iDefender);
|
||||
List<Card> attacking = combat.getAttackersOf(defender);
|
||||
CardLists.sortByPowerAsc(attacking);
|
||||
for (Card atta : attacking) {
|
||||
if (attackNum >= blockNum || !CombatUtil.canBeBlocked(attacker, this.blockers)) {
|
||||
if (attackNum >= blockNum || !CombatUtil.canBeBlocked(attacker, this.blockers, combat)) {
|
||||
damage += ComputerUtilCombat.damageIfUnblocked(atta, opp, null);
|
||||
} else if (CombatUtil.canBeBlocked(attacker, this.blockers)) {
|
||||
} else if (CombatUtil.canBeBlocked(attacker, this.blockers, combat)) {
|
||||
attackNum++;
|
||||
}
|
||||
}
|
||||
// if enough damage: switch to next planeswalker or player
|
||||
if (damage >= pw.getCounters(CounterType.LOYALTY)) {
|
||||
iDefender--;
|
||||
defender = combat.getDefenders().get(iDefender);
|
||||
List<Card> pwDefending = combat.getDefendingPlaneswalkers();
|
||||
boolean found = false;
|
||||
// look for next planeswalker
|
||||
for (Card walker : pwDefending) {
|
||||
if (combat.getAttackersOf(walker).isEmpty()) {
|
||||
defender = walker;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
defender = getDefendingPlayers(combat).get(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return combat;
|
||||
} // getAttackers()
|
||||
|
||||
/**
|
||||
@@ -832,7 +874,7 @@ public class AiAttackController {
|
||||
* </p>
|
||||
*
|
||||
* @param c
|
||||
* a {@link forge.Card} object.
|
||||
* a {@link forge.game.card.Card} object.
|
||||
* @return a int.
|
||||
*/
|
||||
public final int getAttack(final Card c) {
|
||||
@@ -851,11 +893,11 @@ public class AiAttackController {
|
||||
* </p>
|
||||
*
|
||||
* @param attacker
|
||||
* a {@link forge.Card} object.
|
||||
* a {@link forge.game.card.Card} object.
|
||||
* @param defenders
|
||||
* a {@link forge.CardList} object.
|
||||
* @param combat
|
||||
* a {@link forge.game.phase.Combat} object.
|
||||
* a {@link forge.game.combat.Combat} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
public final boolean shouldAttack(final Player ai, final Card attacker, final List<Card> defenders, final Combat combat) {
|
||||
@@ -934,9 +976,7 @@ public class AiAttackController {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (numberOfPossibleBlockers > 1
|
||||
|| (numberOfPossibleBlockers == 1
|
||||
&& !attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures."))) {
|
||||
if (numberOfPossibleBlockers > 1 || (numberOfPossibleBlockers == 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, combat))) {
|
||||
canBeBlocked = true;
|
||||
}
|
||||
/*System.out.println(attacker + " canBeKilledByOne: " + canBeKilledByOne + " canKillAll: "
|
||||
808
forge-ai/src/main/java/forge/ai/AiBlockController.java
Normal file
808
forge-ai/src/main/java/forge/ai/AiBlockController.java
Normal file
@@ -0,0 +1,808 @@
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2011 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import forge.game.CardTraitBase;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* ComputerUtil_Block2 class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id$
|
||||
*/
|
||||
public class AiBlockController {
|
||||
|
||||
private final Player ai;
|
||||
/** Constant <code>attackers</code>. */
|
||||
private List<Card> attackers = new ArrayList<Card>(); // all attackers
|
||||
/** Constant <code>attackersLeft</code>. */
|
||||
private List<Card> attackersLeft = new ArrayList<Card>(); // keeps track of
|
||||
// all currently
|
||||
// unblocked
|
||||
// attackers
|
||||
/** Constant <code>blockedButUnkilled</code>. */
|
||||
private List<Card> blockedButUnkilled = new ArrayList<Card>(); // blocked
|
||||
// attackers
|
||||
// that
|
||||
// currently
|
||||
// wouldn't be
|
||||
// destroyed
|
||||
/** Constant <code>blockersLeft</code>. */
|
||||
private List<Card> blockersLeft = new ArrayList<Card>(); // keeps track of all
|
||||
// unassigned
|
||||
// blockers
|
||||
private int diff = 0;
|
||||
|
||||
private boolean lifeInDanger = false;
|
||||
public AiBlockController(Player aiPlayer) {
|
||||
this.ai = aiPlayer;
|
||||
}
|
||||
|
||||
// finds the creatures able to block the attacker
|
||||
private List<Card> getPossibleBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft, final boolean solo) {
|
||||
final List<Card> blockers = new ArrayList<Card>();
|
||||
|
||||
for (final Card blocker : blockersLeft) {
|
||||
// if the blocker can block a creature with lure it can't block a
|
||||
// creature without
|
||||
if (CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||
if (solo && blocker.hasKeyword("CARDNAME can't attack or block alone.")) {
|
||||
continue;
|
||||
}
|
||||
blockers.add(blocker);
|
||||
}
|
||||
}
|
||||
|
||||
return blockers;
|
||||
}
|
||||
|
||||
// finds blockers that won't be destroyed
|
||||
private List<Card> getSafeBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) {
|
||||
final List<Card> blockers = new ArrayList<Card>();
|
||||
|
||||
for (final Card b : blockersLeft) {
|
||||
if (!ComputerUtilCombat.canDestroyBlocker(ai, b, attacker, combat, false)) {
|
||||
blockers.add(b);
|
||||
}
|
||||
}
|
||||
|
||||
return blockers;
|
||||
}
|
||||
|
||||
// finds blockers that destroy the attacker
|
||||
private List<Card> getKillingBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) {
|
||||
final List<Card> blockers = new ArrayList<Card>();
|
||||
|
||||
for (final Card b : blockersLeft) {
|
||||
if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, b, combat, false)) {
|
||||
blockers.add(b);
|
||||
}
|
||||
}
|
||||
|
||||
return blockers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private List<List<Card>> sortAttackerByDefender(final Combat combat) {
|
||||
List<GameEntity> defenders = combat.getDefenders();
|
||||
final ArrayList<List<Card>> attackers = new ArrayList<List<Card>>(defenders.size());
|
||||
for (GameEntity defender : defenders) {
|
||||
attackers.add(combat.getAttackersOf(defender));
|
||||
}
|
||||
return attackers;
|
||||
}
|
||||
|
||||
private List<Card> sortPotentialAttackers(final Combat combat) {
|
||||
final List<List<Card>> attackerLists = sortAttackerByDefender(combat);
|
||||
final List<Card> sortedAttackers = new ArrayList<Card>();
|
||||
final List<Card> firstAttacker = attackerLists.get(0);
|
||||
|
||||
final List<GameEntity> defenders = combat.getDefenders();
|
||||
|
||||
|
||||
// Begin with the attackers that pose the biggest threat
|
||||
ComputerUtilCard.sortByEvaluateCreature(firstAttacker);
|
||||
CardLists.sortByPowerDesc(firstAttacker);
|
||||
|
||||
// If I don't have any planeswalkers than sorting doesn't really matter
|
||||
if (defenders.size() == 1) {
|
||||
return firstAttacker;
|
||||
}
|
||||
|
||||
final boolean bLifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat);
|
||||
|
||||
// TODO Add creatures attacking Planeswalkers in order of which we want
|
||||
// to protect
|
||||
// defend planeswalkers with more loyalty before planeswalkers with less
|
||||
// loyalty
|
||||
// if planeswalker will be too difficult to defend don't even bother
|
||||
for (List<Card> attacker : attackerLists) {
|
||||
// Begin with the attackers that pose the biggest threat
|
||||
CardLists.sortByPowerDesc(attacker);
|
||||
for (final Card c : attacker) {
|
||||
sortedAttackers.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (bLifeInDanger) {
|
||||
// add creatures attacking the Player to the front of the list
|
||||
for (final Card c : firstAttacker) {
|
||||
sortedAttackers.add(0, c);
|
||||
}
|
||||
|
||||
} else {
|
||||
// add creatures attacking the Player to the back of the list
|
||||
for (final Card c : firstAttacker) {
|
||||
sortedAttackers.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
return sortedAttackers;
|
||||
}
|
||||
|
||||
// ======================= block assignment functions
|
||||
// ================================
|
||||
|
||||
// Good Blocks means a good trade or no trade
|
||||
private void makeGoodBlocks(final Combat combat) {
|
||||
|
||||
List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
|
||||
|
||||
for (final Card attacker : attackersLeft) {
|
||||
|
||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless " +
|
||||
"all creatures defending player controls block it.")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Card blocker = null;
|
||||
|
||||
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||
|
||||
final List<Card> safeBlockers = getSafeBlockers(combat, attacker, blockers);
|
||||
List<Card> killingBlockers;
|
||||
|
||||
if (safeBlockers.size() > 0) {
|
||||
// 1.Blockers that can destroy the attacker but won't get
|
||||
// destroyed
|
||||
killingBlockers = getKillingBlockers(combat, attacker, safeBlockers);
|
||||
if (!killingBlockers.isEmpty()) {
|
||||
blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
|
||||
} else if (!attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
||||
blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers);
|
||||
blockedButUnkilled.add(attacker);
|
||||
}
|
||||
} // no safe blockers
|
||||
else {
|
||||
// 3.Blockers that can destroy the attacker and have an upside when dying
|
||||
killingBlockers = getKillingBlockers(combat, attacker, blockers);
|
||||
for (Card b : killingBlockers) {
|
||||
if ((b.hasKeyword("Undying") && b.getCounters(CounterType.P1P1) == 0)
|
||||
|| !b.getSVar("SacMe").equals("")) {
|
||||
blocker = b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 4.Blockers that can destroy the attacker and are worth less
|
||||
if (blocker == null && !killingBlockers.isEmpty()) {
|
||||
final Card worst = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
|
||||
int value = ComputerUtilCard.evaluateCreature(attacker);
|
||||
|
||||
// check for triggers when unblocked
|
||||
for (Trigger trigger : attacker.getTriggers()) {
|
||||
final Map<String, String> trigParams = trigger.getMapParams();
|
||||
TriggerType mode = trigger.getMode();
|
||||
|
||||
if (!trigger.requirementsCheck(attacker.getGame())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mode == TriggerType.DamageDone) {
|
||||
if ((!trigParams.containsKey("ValidSource")
|
||||
|| CardTraitBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), attacker))
|
||||
&& attacker.getNetCombatDamage() > 0
|
||||
&& (!trigParams.containsKey("ValidTarget")
|
||||
|| CardTraitBase.matchesValid(combat.getDefenderByAttacker(attacker), trigParams.get("ValidTarget").split(","), attacker))) {
|
||||
value += 50;
|
||||
}
|
||||
} else if (mode == TriggerType.AttackerUnblocked) {
|
||||
if (CardTraitBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), attacker)) {
|
||||
value += 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((ComputerUtilCard.evaluateCreature(worst) + diff) < value) {
|
||||
blocker = worst;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (blocker != null) {
|
||||
currentAttackers.remove(attacker);
|
||||
combat.addBlocker(attacker, blocker);
|
||||
}
|
||||
}
|
||||
attackersLeft = (new ArrayList<Card>(currentAttackers));
|
||||
}
|
||||
|
||||
// Good Gang Blocks means a good trade or no trade
|
||||
/**
|
||||
* <p>
|
||||
* makeGangBlocks.
|
||||
* </p>
|
||||
*
|
||||
* @param combat
|
||||
* a {@link forge.game.combat.Combat} object.
|
||||
* @return a {@link forge.game.combat.Combat} object.
|
||||
*/
|
||||
static final Predicate<Card> rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CantBeBlockedByAmount GT"));
|
||||
|
||||
private void makeGangBlocks(final Combat combat) {
|
||||
List<Card> currentAttackers = CardLists.filter(attackersLeft, Predicates.not(rampagesOrNeedsManyToBlock));
|
||||
List<Card> blockers;
|
||||
|
||||
// Try to block an attacker without first strike with a gang of first strikers
|
||||
for (final Card attacker : attackersLeft) {
|
||||
if (!attacker.hasKeyword("First Strike") && !attacker.hasKeyword("Double Strike")) {
|
||||
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
|
||||
final List<Card> firstStrikeBlockers = new ArrayList<Card>();
|
||||
final List<Card> blockGang = new ArrayList<Card>();
|
||||
for (int i = 0; i < blockers.size(); i++) {
|
||||
if (blockers.get(i).hasFirstStrike() || blockers.get(i).hasDoubleStrike()) {
|
||||
firstStrikeBlockers.add(blockers.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (firstStrikeBlockers.size() > 1) {
|
||||
CardLists.sortByPowerDesc(firstStrikeBlockers);
|
||||
for (final Card blocker : firstStrikeBlockers) {
|
||||
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
||||
// if the total damage of the blockgang was not enough
|
||||
// without but is enough with this blocker finish the
|
||||
// blockgang
|
||||
if (ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang) < damageNeeded
|
||||
|| CombatUtil.needsBlockers(attacker) > blockGang.size()) {
|
||||
blockGang.add(blocker);
|
||||
if (ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang) >= damageNeeded) {
|
||||
currentAttackers.remove(attacker);
|
||||
for (final Card b : blockGang) {
|
||||
if (CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||
combat.addBlocker(attacker, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attackersLeft = (new ArrayList<Card>(currentAttackers));
|
||||
currentAttackers = new ArrayList<Card>(attackersLeft);
|
||||
|
||||
// Try to block an attacker with two blockers of which only one will die
|
||||
for (final Card attacker : attackersLeft) {
|
||||
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
|
||||
List<Card> usableBlockers;
|
||||
final List<Card> blockGang = new ArrayList<Card>();
|
||||
int absorbedDamage = 0; // The amount of damage needed to kill the first blocker
|
||||
int currentValue = 0; // The value of the creatures in the blockgang
|
||||
|
||||
// AI can't handle good triple blocks yet
|
||||
if (CombatUtil.needsBlockers(attacker) > 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to add blockers that could be destroyed, but are worth less than the attacker
|
||||
// Don't use blockers without First Strike or Double Strike if attacker has it
|
||||
usableBlockers = CardLists.filter(blockers, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if ((attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike"))
|
||||
&& !(c.hasKeyword("First Strike") || c.hasKeyword("Double Strike"))) {
|
||||
return false;
|
||||
}
|
||||
return lifeInDanger || (ComputerUtilCard.evaluateCreature(c) + diff) < ComputerUtilCard.evaluateCreature(attacker);
|
||||
}
|
||||
});
|
||||
if (usableBlockers.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Card leader = ComputerUtilCard.getBestCreatureAI(usableBlockers);
|
||||
blockGang.add(leader);
|
||||
usableBlockers.remove(leader);
|
||||
absorbedDamage = ComputerUtilCombat.getEnoughDamageToKill(leader, attacker.getNetCombatDamage(), attacker, true);
|
||||
currentValue = ComputerUtilCard.evaluateCreature(leader);
|
||||
|
||||
for (final Card blocker : usableBlockers) {
|
||||
// Add an additional blocker if the current blockers are not
|
||||
// enough and the new one would deal the remaining damage
|
||||
final int currentDamage = ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang);
|
||||
final int additionalDamage = ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker);
|
||||
final int absorbedDamage2 = ComputerUtilCombat.getEnoughDamageToKill(blocker, attacker.getNetCombatDamage(), attacker, true);
|
||||
final int addedValue = ComputerUtilCard.evaluateCreature(blocker);
|
||||
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
||||
if ((damageNeeded > currentDamage || CombatUtil.needsBlockers(attacker) > blockGang.size())
|
||||
&& !(damageNeeded > currentDamage + additionalDamage)
|
||||
// The attacker will be killed
|
||||
&& (absorbedDamage2 + absorbedDamage > attacker.getNetCombatDamage()
|
||||
// only one blocker can be killed
|
||||
|| currentValue + addedValue - 50 <= ComputerUtilCard.evaluateCreature(attacker)
|
||||
// or attacker is worth more
|
||||
|| (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)))
|
||||
// or life is in danger
|
||||
&& CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||
// this is needed for attackers that can't be blocked by
|
||||
// more than 1
|
||||
currentAttackers.remove(attacker);
|
||||
combat.addBlocker(attacker, blocker);
|
||||
if (CombatUtil.canBlock(attacker, leader, combat)) {
|
||||
combat.addBlocker(attacker, leader);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attackersLeft = (new ArrayList<Card>(currentAttackers));
|
||||
}
|
||||
|
||||
// Bad Trade Blocks (should only be made if life is in danger)
|
||||
/**
|
||||
* <p>
|
||||
* makeTradeBlocks.
|
||||
* </p>
|
||||
*
|
||||
* @param combat
|
||||
* a {@link forge.game.combat.Combat} object.
|
||||
* @return a {@link forge.game.combat.Combat} object.
|
||||
*/
|
||||
private void makeTradeBlocks(final Combat combat) {
|
||||
|
||||
List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
|
||||
List<Card> killingBlockers;
|
||||
|
||||
for (final Card attacker : attackersLeft) {
|
||||
|
||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless " +
|
||||
"all creatures defending player controls block it.")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<Card> possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||
killingBlockers = getKillingBlockers(combat, attacker, possibleBlockers);
|
||||
if (!killingBlockers.isEmpty() && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
final Card blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers);
|
||||
combat.addBlocker(attacker, blocker);
|
||||
currentAttackers.remove(attacker);
|
||||
}
|
||||
}
|
||||
attackersLeft = (new ArrayList<Card>(currentAttackers));
|
||||
}
|
||||
|
||||
// Chump Blocks (should only be made if life is in danger)
|
||||
private void makeChumpBlocks(final Combat combat) {
|
||||
|
||||
List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
|
||||
|
||||
makeChumpBlocks(combat, currentAttackers);
|
||||
|
||||
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
makeMultiChumpBlocks(combat);
|
||||
}
|
||||
}
|
||||
|
||||
private void makeChumpBlocks(final Combat combat, List<Card> attackers) {
|
||||
|
||||
if (attackers.isEmpty() || !ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Card attacker = attackers.get(0);
|
||||
|
||||
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|
||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||
attackers.remove(0);
|
||||
makeChumpBlocks(combat, attackers);
|
||||
return;
|
||||
}
|
||||
|
||||
List<Card> chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||
if (!chumpBlockers.isEmpty()) {
|
||||
final Card blocker = ComputerUtilCard.getWorstCreatureAI(chumpBlockers);
|
||||
|
||||
// check if it's better to block a creature with lower power and without trample
|
||||
if (attacker.hasKeyword("Trample")) {
|
||||
final int damageAbsorbed = blocker.getLethalDamage();
|
||||
if (attacker.getNetCombatDamage() > damageAbsorbed) {
|
||||
for (Card other : attackers) {
|
||||
if (other.equals(attacker)) {
|
||||
continue;
|
||||
}
|
||||
if (other.getNetCombatDamage() >= damageAbsorbed
|
||||
&& !other.hasKeyword("Trample")
|
||||
&& CombatUtil.canBlock(other, blocker, combat)) {
|
||||
combat.addBlocker(other, blocker);
|
||||
attackersLeft.remove(other);
|
||||
blockedButUnkilled.add(other);
|
||||
attackers.remove(other);
|
||||
makeChumpBlocks(combat, attackers);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
combat.addBlocker(attacker, blocker);
|
||||
attackersLeft.remove(attacker);
|
||||
blockedButUnkilled.add(attacker);
|
||||
}
|
||||
attackers.remove(0);
|
||||
makeChumpBlocks(combat, attackers);
|
||||
}
|
||||
|
||||
// Block creatures with "can't be blocked except by two or more creatures"
|
||||
private void makeMultiChumpBlocks(final Combat combat) {
|
||||
|
||||
List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
|
||||
|
||||
for (final Card attacker : currentAttackers) {
|
||||
|
||||
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|
||||
&& !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||
continue;
|
||||
}
|
||||
List<Card> possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||
if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, possibleBlockers.size(), combat)) {
|
||||
continue;
|
||||
}
|
||||
List<Card> usedBlockers = new ArrayList<Card>();
|
||||
for (Card blocker : possibleBlockers) {
|
||||
if (CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||
combat.addBlocker(attacker, blocker);
|
||||
usedBlockers.add(blocker);
|
||||
if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size(), combat)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size(), combat)) {
|
||||
attackersLeft.remove(attacker);
|
||||
} else {
|
||||
for (Card blocker : usedBlockers) {
|
||||
combat.removeBlockAssignment(attacker, blocker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Reinforce blockers blocking attackers with trample (should only be made if life is in danger) */
|
||||
private void reinforceBlockersAgainstTrample(final Combat combat) {
|
||||
|
||||
List<Card> chumpBlockers;
|
||||
|
||||
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, "Trample");
|
||||
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
|
||||
|
||||
// TODO - should check here for a "rampage-like" trigger that replaced
|
||||
// the keyword:
|
||||
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
|
||||
|
||||
for (final Card attacker : tramplingAttackers) {
|
||||
|
||||
if ((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") && !combat.isBlocked(attacker))
|
||||
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|
||||
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
|
||||
chumpBlockers.removeAll(combat.getBlockers(attacker));
|
||||
for (final Card blocker : chumpBlockers) {
|
||||
// Add an additional blocker if the current blockers are not
|
||||
// enough and the new one would suck some of the damage
|
||||
if (ComputerUtilCombat.getAttack(attacker) > ComputerUtilCombat.totalShieldDamage(attacker, combat.getBlockers(attacker))
|
||||
&& ComputerUtilCombat.shieldDamage(attacker, blocker) > 0
|
||||
&& CombatUtil.canBlock(attacker, blocker, combat) && ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
combat.addBlocker(attacker, blocker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Support blockers not destroying the attacker with more blockers to try to kill the attacker */
|
||||
private void reinforceBlockersToKill(final Combat combat) {
|
||||
|
||||
List<Card> safeBlockers;
|
||||
List<Card> blockers;
|
||||
|
||||
List<Card> targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock));
|
||||
|
||||
// TODO - should check here for a "rampage-like" trigger that replaced
|
||||
// the keyword:
|
||||
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
|
||||
|
||||
for (final Card attacker : targetAttackers) {
|
||||
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
|
||||
blockers.removeAll(combat.getBlockers(attacker));
|
||||
|
||||
// Try to use safe blockers first
|
||||
safeBlockers = getSafeBlockers(combat, attacker, blockers);
|
||||
for (final Card blocker : safeBlockers) {
|
||||
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
||||
// Add an additional blocker if the current blockers are not
|
||||
// enough and the new one would deal additional damage
|
||||
if ((damageNeeded > ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker)))
|
||||
&& ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker) > 0
|
||||
&& CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||
combat.addBlocker(attacker, blocker);
|
||||
}
|
||||
blockers.remove(blocker); // Don't check them again next
|
||||
}
|
||||
|
||||
// Try to add blockers that could be destroyed, but are worth less
|
||||
// than the attacker
|
||||
// Don't use blockers without First Strike or Double Strike if
|
||||
// attacker has it
|
||||
if (attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike")) {
|
||||
safeBlockers = CardLists.getKeyword(blockers, "First Strike");
|
||||
safeBlockers.addAll(CardLists.getKeyword(blockers, "Double Strike"));
|
||||
} else {
|
||||
safeBlockers = new ArrayList<Card>(blockers);
|
||||
}
|
||||
|
||||
for (final Card blocker : safeBlockers) {
|
||||
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
|
||||
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
|
||||
// Add an additional blocker if the current blockers are not
|
||||
// enough and the new one would deal the remaining damage
|
||||
final int currentDamage = ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker));
|
||||
final int additionalDamage = ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker);
|
||||
if ((damageNeeded > currentDamage)
|
||||
&& !(damageNeeded > (currentDamage + additionalDamage))
|
||||
&& ((ComputerUtilCard.evaluateCreature(blocker) + diff) < ComputerUtilCard
|
||||
.evaluateCreature(attacker)) && CombatUtil.canBlock(attacker, blocker, combat)) {
|
||||
combat.addBlocker(attacker, blocker);
|
||||
blockersLeft.remove(blocker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clearBlockers(final Combat combat, final List<Card> possibleBlockers) {
|
||||
|
||||
final List<Card> oldBlockers = combat.getAllBlockers();
|
||||
for (final Card blocker : oldBlockers) {
|
||||
if ( blocker.getController() == ai ) // don't touch other player's blockers
|
||||
combat.removeFromCombat(blocker);
|
||||
}
|
||||
|
||||
attackersLeft = new ArrayList<Card>(attackers); // keeps track of all currently unblocked attackers
|
||||
blockersLeft = new ArrayList<Card>(possibleBlockers); // keeps track of all unassigned blockers
|
||||
blockedButUnkilled = new ArrayList<Card>(); // keeps track of all blocked attackers that currently wouldn't be destroyed
|
||||
}
|
||||
|
||||
/** Assigns blockers for the provided combat instance (in favor of player passes to ctor) */
|
||||
public void assignBlockers(final Combat combat) {
|
||||
|
||||
final List<Card> possibleBlockers = ai.getCreaturesInPlay();
|
||||
|
||||
attackers = sortPotentialAttackers(combat);
|
||||
|
||||
if (attackers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearBlockers(combat, possibleBlockers);
|
||||
|
||||
List<Card> blockers;
|
||||
List<Card> chumpBlockers;
|
||||
|
||||
diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade
|
||||
|
||||
// remove all attackers that can't be blocked anyway
|
||||
for (final Card a : attackers) {
|
||||
if (!CombatUtil.canBeBlocked(a, ai)) {
|
||||
attackersLeft.remove(a);
|
||||
}
|
||||
}
|
||||
|
||||
// remove all blockers that can't block anyway
|
||||
for (final Card b : possibleBlockers) {
|
||||
if (!CombatUtil.canBlock(b, combat)) {
|
||||
blockersLeft.remove(b);
|
||||
}
|
||||
}
|
||||
|
||||
if (attackersLeft.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Begin with the weakest blockers
|
||||
CardLists.sortByPowerAsc(blockersLeft);
|
||||
|
||||
// == 1. choose best blocks first ==
|
||||
makeGoodBlocks(combat);
|
||||
makeGangBlocks(combat);
|
||||
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
makeTradeBlocks(combat); // choose necessary trade blocks
|
||||
}
|
||||
// if life is in danger
|
||||
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
makeChumpBlocks(combat); // choose necessary chump blocks
|
||||
}
|
||||
// if life is still in danger
|
||||
// Reinforce blockers blocking attackers with trample if life is still
|
||||
// in danger
|
||||
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
reinforceBlockersAgainstTrample(combat);
|
||||
}
|
||||
// Support blockers not destroying the attacker with more blockers to
|
||||
// try to kill the attacker
|
||||
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
reinforceBlockersToKill(combat);
|
||||
}
|
||||
|
||||
// == 2. If the AI life would still be in danger make a safer approach ==
|
||||
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
lifeInDanger = true;
|
||||
clearBlockers(combat, possibleBlockers); // reset every block assignment
|
||||
makeTradeBlocks(combat); // choose necessary trade blocks
|
||||
// if life is in danger
|
||||
makeGoodBlocks(combat);
|
||||
// choose necessary chump blocks if life is still in danger
|
||||
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
makeChumpBlocks(combat);
|
||||
}
|
||||
// Reinforce blockers blocking attackers with trample if life is
|
||||
// still in danger
|
||||
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
reinforceBlockersAgainstTrample(combat);
|
||||
}
|
||||
makeGangBlocks(combat);
|
||||
reinforceBlockersToKill(combat);
|
||||
}
|
||||
|
||||
// == 3. If the AI life would be in serious danger make an even safer approach ==
|
||||
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
||||
clearBlockers(combat, possibleBlockers); // reset every block assignment
|
||||
makeChumpBlocks(combat); // choose chump blocks
|
||||
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
makeTradeBlocks(combat); // choose necessary trade
|
||||
}
|
||||
|
||||
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
makeGoodBlocks(combat);
|
||||
}
|
||||
// Reinforce blockers blocking attackers with trample if life is
|
||||
// still in danger
|
||||
else {
|
||||
reinforceBlockersAgainstTrample(combat);
|
||||
}
|
||||
makeGangBlocks(combat);
|
||||
// Support blockers not destroying the attacker with more blockers
|
||||
// to try to kill the attacker
|
||||
reinforceBlockersToKill(combat);
|
||||
}
|
||||
|
||||
// assign blockers that have to block
|
||||
chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each turn if able.");
|
||||
// if an attacker with lure attacks - all that can block
|
||||
for (final Card blocker : blockersLeft) {
|
||||
if (CombatUtil.mustBlockAnAttacker(blocker, combat)) {
|
||||
chumpBlockers.add(blocker);
|
||||
}
|
||||
}
|
||||
if (!chumpBlockers.isEmpty()) {
|
||||
CardLists.shuffle(attackers);
|
||||
for (final Card attacker : attackers) {
|
||||
blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
|
||||
for (final Card blocker : blockers) {
|
||||
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
|
||||
&& (CombatUtil.mustBlockAnAttacker(blocker, combat)
|
||||
|| blocker.hasKeyword("CARDNAME blocks each turn if able."))) {
|
||||
combat.addBlocker(attacker, blocker);
|
||||
if (blocker.getMustBlockCards() != null) {
|
||||
int mustBlockAmt = blocker.getMustBlockCards().size();
|
||||
List<Card> blockedSoFar = combat.getAttackersBlockedBy(blocker);
|
||||
boolean canBlockAnother = CombatUtil.canBlockMoreCreatures(blocker, blockedSoFar);
|
||||
if (!canBlockAnother || mustBlockAmt == blockedSoFar.size()) {
|
||||
blockersLeft.remove(blocker);
|
||||
}
|
||||
} else {
|
||||
blockersLeft.remove(blocker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Card> orderBlockers(Card attacker, List<Card> blockers) {
|
||||
// ordering of blockers, sort by evaluate, then try to kill the best
|
||||
int damage = attacker.getNetCombatDamage();
|
||||
ComputerUtilCard.sortByEvaluateCreature(blockers);
|
||||
final List<Card> first = new ArrayList<Card>();
|
||||
final List<Card> last = new ArrayList<Card>();
|
||||
for (Card blocker : blockers) {
|
||||
int lethal = ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true);
|
||||
if (lethal > damage) {
|
||||
last.add(blocker);
|
||||
} else {
|
||||
first.add(blocker);
|
||||
damage -= lethal;
|
||||
}
|
||||
}
|
||||
first.addAll(last);
|
||||
|
||||
// TODO: Take total damage, and attempt to maximize killing the greatest evaluation of creatures
|
||||
// It's probably generally better to kill the largest creature, but sometimes its better to kill a few smaller ones
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
public static List<Card> orderAttackers(Card blocker, List<Card> attackers) {
|
||||
// This shouldn't really take trample into account, but otherwise should be pretty similar to orderBlockers
|
||||
// ordering of blockers, sort by evaluate, then try to kill the best
|
||||
int damage = blocker.getNetCombatDamage();
|
||||
ComputerUtilCard.sortByEvaluateCreature(attackers);
|
||||
final List<Card> first = new ArrayList<Card>();
|
||||
final List<Card> last = new ArrayList<Card>();
|
||||
for (Card attacker : attackers) {
|
||||
int lethal = ComputerUtilCombat.getEnoughDamageToKill(attacker, damage, blocker, true);
|
||||
if (lethal > damage) {
|
||||
last.add(attacker);
|
||||
} else {
|
||||
first.add(attacker);
|
||||
damage -= lethal;
|
||||
}
|
||||
}
|
||||
first.addAll(last);
|
||||
|
||||
// TODO: Take total damage, and attempt to maximize killing the greatest evaluation of creatures
|
||||
// It's probably generally better to kill the largest creature, but sometimes its better to kill a few smaller ones
|
||||
|
||||
return first;
|
||||
}
|
||||
}
|
||||
1458
forge-ai/src/main/java/forge/ai/AiController.java
Normal file
1458
forge-ai/src/main/java/forge/ai/AiController.java
Normal file
File diff suppressed because it is too large
Load Diff
591
forge-ai/src/main/java/forge/ai/AiCostDecision.java
Normal file
591
forge-ai/src/main/java/forge/ai/AiCostDecision.java
Normal file
@@ -0,0 +1,591 @@
|
||||
package forge.ai;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.card.CardType;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.*;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class AiCostDecision extends CostDecisionMakerBase implements ICostVisitor<PaymentDecision> {
|
||||
|
||||
|
||||
private final SpellAbility ability;
|
||||
private final Card source;
|
||||
|
||||
public AiCostDecision(Player ai0, SpellAbility sa) {
|
||||
super(ai0);
|
||||
ability = sa;
|
||||
source = ability.getHostCard();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostAddMana cost) {
|
||||
Integer c = cost.convertAmount();
|
||||
|
||||
if (c == null) {
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
|
||||
return PaymentDecision.number(c);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostChooseCreatureType cost) {
|
||||
String choice = player.getController().chooseSomeType("Creature", ability, new ArrayList<String>(CardType.getCreatureTypes()), new ArrayList<String>());
|
||||
return PaymentDecision.type(choice);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostDiscard cost) {
|
||||
final String type = cost.getType();
|
||||
|
||||
final List<Card> hand = player.getCardsIn(ZoneType.Hand);
|
||||
if (type.equals("LastDrawn")) {
|
||||
if (!hand.contains(player.getLastDrawnCard())) {
|
||||
return null;
|
||||
}
|
||||
return PaymentDecision.card(player.getLastDrawnCard());
|
||||
}
|
||||
else if (cost.payCostFromSource()) {
|
||||
if (!hand.contains(source)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return PaymentDecision.card(source);
|
||||
}
|
||||
else if (type.equals("Hand")) {
|
||||
return PaymentDecision.card(hand);
|
||||
}
|
||||
|
||||
if (type.contains("WithSameName")) {
|
||||
return null;
|
||||
}
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
if (sVar.equals("XChoice")) {
|
||||
return null;
|
||||
}
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
|
||||
if (type.equals("Random")) {
|
||||
return PaymentDecision.card(CardLists.getRandomSubList(hand, c));
|
||||
}
|
||||
else {
|
||||
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
||||
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostDamage cost) {
|
||||
Integer c = cost.convertAmount();
|
||||
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
// Generalize cost
|
||||
if (sVar.equals("XChoice")) {
|
||||
return null; // cannot pay
|
||||
} else {
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
}
|
||||
|
||||
return PaymentDecision.number(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostDraw cost) {
|
||||
Integer c = cost.convertAmount();
|
||||
|
||||
if (c == null) {
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
|
||||
return PaymentDecision.number(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostExile cost) {
|
||||
if (cost.payCostFromSource()) {
|
||||
return PaymentDecision.card(source);
|
||||
}
|
||||
|
||||
if (cost.getType().equals("All")) {
|
||||
return PaymentDecision.card(player.getCardsIn(cost.getFrom()));
|
||||
}
|
||||
else if (cost.getType().contains("FromTopGrave")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
// Generalize cost
|
||||
if (sVar.equals("XChoice")) {
|
||||
return null;
|
||||
}
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
|
||||
if (cost.getFrom().equals(ZoneType.Library)) {
|
||||
return PaymentDecision.card(player.getCardsIn(ZoneType.Library, c));
|
||||
}
|
||||
else if (cost.sameZone) {
|
||||
// TODO Determine exile from same zone for AI
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
List<Card> chosen = ComputerUtil.chooseExileFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c);
|
||||
return null == chosen ? null : PaymentDecision.card(chosen);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostExiledMoveToGrave cost) {
|
||||
Integer c = cost.convertAmount();
|
||||
List<Card> chosen = new ArrayList<Card>();
|
||||
|
||||
if (c == null) {
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
|
||||
List<Card> typeList = player.getGame().getCardsIn(ZoneType.Exile);
|
||||
|
||||
typeList = CardLists.getValidCards(typeList, cost.getType().split(";"), player, source);
|
||||
|
||||
if (typeList.size() < c) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CardLists.sortByPowerAsc(typeList);
|
||||
Collections.reverse(typeList);
|
||||
|
||||
for (int i = 0; i < c; i++) {
|
||||
chosen.add(typeList.get(i));
|
||||
}
|
||||
|
||||
return chosen.isEmpty() ? null : PaymentDecision.card(chosen);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostFlipCoin cost) {
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
// Generalize cost
|
||||
if (sVar.equals("XChoice")) {
|
||||
return null;
|
||||
}
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
return PaymentDecision.number(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostGainControl cost) {
|
||||
if (cost.payCostFromSource())
|
||||
return PaymentDecision.card(source);
|
||||
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
|
||||
final List<Card> typeList = CardLists.getValidCards(player.getGame().getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), player, source);
|
||||
|
||||
|
||||
if (typeList.size() < c) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CardLists.sortByPowerAsc(typeList);
|
||||
final List<Card> res = new ArrayList<Card>();
|
||||
|
||||
for (int i = 0; i < c; i++) {
|
||||
res.add(typeList.get(i));
|
||||
}
|
||||
return res.isEmpty() ? null : PaymentDecision.card(res);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostGainLife cost) {
|
||||
final List<Player> oppsThatCanGainLife = new ArrayList<Player>();
|
||||
|
||||
for (final Player opp : cost.getPotentialTargets(player, source)) {
|
||||
if (opp.canGainLife()) {
|
||||
oppsThatCanGainLife.add(opp);
|
||||
}
|
||||
}
|
||||
|
||||
if (oppsThatCanGainLife.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return PaymentDecision.players(oppsThatCanGainLife);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostMill cost) {
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
// Generalize cost
|
||||
if (sVar.equals("XChoice")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
|
||||
List<Card> topLib = player.getCardsIn(ZoneType.Library, c);
|
||||
return topLib.size() < c ? null : PaymentDecision.card(topLib);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostPartMana cost) {
|
||||
return PaymentDecision.number(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostPayLife cost) {
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
// Generalize cost
|
||||
if (sVar.equals("XChoice")) {
|
||||
return null;
|
||||
} else {
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
}
|
||||
if (!player.canPayLife(c)) {
|
||||
return null;
|
||||
}
|
||||
// activator.payLife(c, null);
|
||||
return PaymentDecision.number(c);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostPutCardToLib cost) {
|
||||
Integer c = cost.convertAmount();
|
||||
final Game game = player.getGame();
|
||||
List<Card> chosen = new ArrayList<Card>();
|
||||
List<Card> list;
|
||||
|
||||
if (cost.isSameZone()) {
|
||||
list = new ArrayList<Card>(game.getCardsIn(cost.getFrom()));
|
||||
} else {
|
||||
list = new ArrayList<Card>(player.getCardsIn(cost.getFrom()));
|
||||
}
|
||||
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
// Generalize cost
|
||||
if (sVar.equals("XChoice")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
|
||||
list = CardLists.getValidCards(list, cost.getType().split(";"), player, source);
|
||||
|
||||
if (cost.isSameZone()) {
|
||||
// Jotun Grunt
|
||||
// TODO: improve AI
|
||||
final List<Player> players = game.getPlayers();
|
||||
for (Player p : players) {
|
||||
List<Card> enoughType = CardLists.filter(list, CardPredicates.isOwner(p));
|
||||
if (enoughType.size() >= c) {
|
||||
chosen.addAll(enoughType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
chosen = chosen.subList(0, c);
|
||||
} else {
|
||||
chosen = ComputerUtil.choosePutToLibraryFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c);
|
||||
}
|
||||
return chosen.isEmpty() ? null : PaymentDecision.card(chosen);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostPutCounter cost) {
|
||||
|
||||
if (cost.payCostFromSource()) {
|
||||
return PaymentDecision.card(source);
|
||||
|
||||
}
|
||||
|
||||
final List<Card> typeList = CardLists.getValidCards(player.getGame().getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), player, source);
|
||||
|
||||
Card card = null;
|
||||
if (cost.getType().equals("Creature.YouCtrl")) {
|
||||
card = ComputerUtilCard.getWorstCreatureAI(typeList);
|
||||
} else {
|
||||
card = ComputerUtilCard.getWorstPermanentAI(typeList, false, false, false, false);
|
||||
}
|
||||
return PaymentDecision.card(card);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostTap cost) {
|
||||
return PaymentDecision.number(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostTapType cost) {
|
||||
final String amount = cost.getAmount();
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(amount);
|
||||
if (sVar.equals("XChoice")) {
|
||||
List<Card> typeList =
|
||||
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), ability.getActivatingPlayer(), ability.getHostCard());
|
||||
typeList = CardLists.filter(typeList, Presets.UNTAPPED);
|
||||
c = typeList.size();
|
||||
source.setSVar("ChosenX", "Number$" + Integer.toString(c));
|
||||
} else {
|
||||
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||
}
|
||||
}
|
||||
if (cost.getType().contains("sharesCreatureTypeWith") || cost.getType().contains("withTotalPowerGE")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Card> totap = ComputerUtil.chooseTapType(player, cost.getType(), source, !cost.canTapSource, c);
|
||||
|
||||
|
||||
if (totap == null) {
|
||||
System.out.println("Couldn't find a valid card to tap for: " + source.getName());
|
||||
return null;
|
||||
}
|
||||
|
||||
return PaymentDecision.card(totap);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostSacrifice cost) {
|
||||
if (cost.payCostFromSource()) {
|
||||
return PaymentDecision.card(source);
|
||||
}
|
||||
if (cost.getAmount().equals("All")) {
|
||||
/*List<Card> typeList = new ArrayList<Card>(activator.getCardsIn(ZoneType.Battlefield));
|
||||
typeList = CardLists.getValidCards(typeList, cost.getType().split(";"), activator, source);
|
||||
if (activator.hasKeyword("You can't sacrifice creatures to cast spells or activate abilities.")) {
|
||||
typeList = CardLists.getNotType(typeList, "Creature");
|
||||
}*/
|
||||
// Does the AI want to use Sacrifice All?
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
if (ability.getSVar(cost.getAmount()).equals("XChoice")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
List<Card> list = ComputerUtil.chooseSacrificeType(player, cost.getType(), source, ability.getTargetCard(), c);
|
||||
return PaymentDecision.card(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostReturn cost) {
|
||||
if (cost.payCostFromSource())
|
||||
return PaymentDecision.card(source);
|
||||
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
|
||||
List<Card> res = ComputerUtil.chooseReturnType(player, cost.getType(), source, ability.getTargetCard(), c);
|
||||
return res.isEmpty() ? null : PaymentDecision.card(res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostReveal cost) {
|
||||
|
||||
final String type = cost.getType();
|
||||
List<Card> hand = new ArrayList<Card>(player.getCardsIn(ZoneType.Hand));
|
||||
|
||||
if (cost.payCostFromSource()) {
|
||||
if (!hand.contains(source)) {
|
||||
return null;
|
||||
}
|
||||
return PaymentDecision.card(source);
|
||||
}
|
||||
|
||||
if (cost.getType().equals("Hand"))
|
||||
return PaymentDecision.card(player.getCardsIn(ZoneType.Hand));
|
||||
|
||||
if (cost.getType().equals("SameColor")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
hand = CardLists.getValidCards(hand, type.split(";"), player, source);
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(cost.getAmount());
|
||||
if (sVar.equals("XChoice")) {
|
||||
c = hand.size();
|
||||
} else {
|
||||
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
|
||||
}
|
||||
}
|
||||
|
||||
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
||||
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostRemoveAnyCounter cost) {
|
||||
final String amount = cost.getAmount();
|
||||
final int c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||
final String type = cost.getType();
|
||||
|
||||
List<Card> typeList = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"), player, source);
|
||||
List<Card> hperms = CardLists.filter(typeList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
for (final CounterType c1 : CounterType.values()) {
|
||||
if (crd.getCounters(c1) >= c && ComputerUtil.isNegativeCounter(c1, crd)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if(hperms.isEmpty())
|
||||
return null;
|
||||
|
||||
PaymentDecision result = PaymentDecision.card(hperms);
|
||||
Card valid = hperms.get(0);
|
||||
for (CounterType c1 : valid.getCounters().keySet()) {
|
||||
if (valid.getCounters(c1) >= c && ComputerUtil.isNegativeCounter(c1, valid)) {
|
||||
result.ct = c1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Only find cards with enough negative counters
|
||||
// TODO: add ai for Chisei, Heart of Oceans
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostRemoveCounter cost) {
|
||||
final String amount = cost.getAmount();
|
||||
Integer c = cost.convertAmount();
|
||||
final String type = cost.getType();
|
||||
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(amount);
|
||||
if (sVar.equals("XChoice")) {
|
||||
return null;
|
||||
}
|
||||
if (amount.equals("All")) {
|
||||
c = source.getCounters(cost.counter);
|
||||
} else {
|
||||
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cost.payCostFromSource()) {
|
||||
List<Card> typeList;
|
||||
if (type.equals("OriginalHost")) {
|
||||
typeList = Lists.newArrayList(ability.getOriginalHost());
|
||||
} else {
|
||||
typeList = CardLists.getValidCards(player.getCardsIn(cost.zone), type.split(";"), player, source);
|
||||
}
|
||||
for (Card card : typeList) {
|
||||
if (card.getCounters(cost.counter) >= c) {
|
||||
return PaymentDecision.card(card, c);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (c > source.getCounters(cost.counter)) {
|
||||
System.out.println("Not enough " + cost.counter + " on " + source.getName());
|
||||
return null;
|
||||
}
|
||||
|
||||
return PaymentDecision.card(source, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostUntapType cost) {
|
||||
final String amount = cost.getAmount();
|
||||
Integer c = cost.convertAmount();
|
||||
if (c == null) {
|
||||
final String sVar = ability.getSVar(amount);
|
||||
if (sVar.equals("XChoice")) {
|
||||
List<Card> typeList = player.getGame().getCardsIn(ZoneType.Battlefield);
|
||||
typeList = CardLists.getValidCards(typeList, cost.getType().split(";"), player, ability.getHostCard());
|
||||
if (!cost.canUntapSource) {
|
||||
typeList.remove(source);
|
||||
}
|
||||
typeList = CardLists.filter(typeList, Presets.TAPPED);
|
||||
c = typeList.size();
|
||||
source.setSVar("ChosenX", "Number$" + Integer.toString(c));
|
||||
} else {
|
||||
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||
}
|
||||
}
|
||||
|
||||
List<Card> list = ComputerUtil.chooseUntapType(player, cost.getType(), source, cost.canUntapSource, c);
|
||||
|
||||
if (list == null) {
|
||||
System.out.println("Couldn't find a valid card to untap for: " + source.getName());
|
||||
return null;
|
||||
}
|
||||
|
||||
return PaymentDecision.card(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostUntap cost) {
|
||||
return PaymentDecision.number(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostUnattach cost) {
|
||||
Card cardToUnattach = cost.findCardToUnattach(source, (Player) player, ability);
|
||||
if (cardToUnattach == null) {
|
||||
// We really shouldn't be able to get here if there's nothing to unattach
|
||||
return null;
|
||||
}
|
||||
return PaymentDecision.card(cardToUnattach);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean paysRightAfterDecision() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
20
forge-ai/src/main/java/forge/ai/AiPlayDecision.java
Normal file
20
forge-ai/src/main/java/forge/ai/AiPlayDecision.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package forge.ai;
|
||||
|
||||
public enum AiPlayDecision {
|
||||
WillPlay,
|
||||
CantPlaySa,
|
||||
CantPlayAi,
|
||||
CantAfford,
|
||||
CantAffordX,
|
||||
WaitForMain2,
|
||||
AnotherTime,
|
||||
MissingNeededCards,
|
||||
NeedsToPlayCriteriaNotMet,
|
||||
TargetingFailed,
|
||||
CostNotAcceptable,
|
||||
WouldDestroyLegend,
|
||||
WouldDestroyOtherPlaneswalker,
|
||||
WouldBecomeZeroToughnessCreature,
|
||||
WouldDestroyWorldEnchantment,
|
||||
BadEtbEffects;
|
||||
}
|
||||
@@ -1,187 +1,192 @@
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2013 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.game.ai;
|
||||
|
||||
import forge.game.player.LobbyPlayer;
|
||||
import forge.game.player.LobbyPlayerAi;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.FileUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Holds default AI personality profile values in an enum.
|
||||
* Loads profile from the given text file when setProfile is called.
|
||||
* If a requested value is not loaded from a profile, default is returned.
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: AIProfile.java 20169 2013-03-08 08:24:17Z Agetian $
|
||||
*/
|
||||
public class AiProfileUtil {
|
||||
private static Map<String, Map<AiProps, String>> loadedProfiles = new HashMap<String, Map<AiProps, String>>();
|
||||
|
||||
private static final String AI_PROFILE_DIR = "res/ai";
|
||||
private static final String AI_PROFILE_EXT = ".ai";
|
||||
|
||||
public static final String AI_PROFILE_RANDOM_MATCH = "* Random (Match) *";
|
||||
public static final String AI_PROFILE_RANDOM_DUEL = "* Random (Duel) *";
|
||||
|
||||
/** Builds an AI profile file name with full relative
|
||||
* path based on the profile name.
|
||||
* @param profileName the name of the profile.
|
||||
* @return the full relative path and file name for the given profile.
|
||||
*/
|
||||
private static String buildFileName(final String profileName) {
|
||||
return String.format("%s/%s%s", AI_PROFILE_DIR, profileName, AI_PROFILE_EXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all profiles
|
||||
*/
|
||||
public static final void loadAllProfiles() {
|
||||
loadedProfiles.clear();
|
||||
ArrayList<String> availableProfiles = getAvailableProfiles();
|
||||
for (String profile : availableProfiles) {
|
||||
loadedProfiles.put(profile, loadProfile(profile));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a single profile.
|
||||
* @param profileName a profile to load.
|
||||
*/
|
||||
private static final Map<AiProps, String> loadProfile(final String profileName) {
|
||||
Map<AiProps, String> profileMap = new HashMap<AiProps, String>();
|
||||
|
||||
List<String> lines = FileUtil.readFile(buildFileName(profileName));
|
||||
for (String line : lines) {
|
||||
|
||||
if (line.startsWith("#") || (line.length() == 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String[] split = line.split("=");
|
||||
|
||||
if (split.length == 2) {
|
||||
profileMap.put(AiProps.valueOf(split[0]), split[1]);
|
||||
} else if (split.length == 1 && line.endsWith("=")) {
|
||||
profileMap.put(AiProps.valueOf(split[0]), "");
|
||||
}
|
||||
}
|
||||
|
||||
return profileMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an AI property value for the current profile.
|
||||
*
|
||||
* @param fp0 an AI property.
|
||||
* @return String
|
||||
*/
|
||||
public static String getAIProp(final LobbyPlayer p, final AiProps fp0) {
|
||||
String val = null;
|
||||
if (!(p instanceof LobbyPlayerAi))
|
||||
return "";
|
||||
String profile = ((LobbyPlayerAi) p).getAiProfile();
|
||||
|
||||
if (loadedProfiles.get(profile) != null) {
|
||||
val = loadedProfiles.get(profile).get(fp0);
|
||||
}
|
||||
if (val == null) { val = fp0.getDefault(); }
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of strings containing all available profiles.
|
||||
* @return ArrayList<String> - an array of strings containing all
|
||||
* available profiles.
|
||||
*/
|
||||
public static ArrayList<String> getAvailableProfiles()
|
||||
{
|
||||
final ArrayList<String> availableProfiles = new ArrayList<String>();
|
||||
|
||||
final File dir = new File(AI_PROFILE_DIR);
|
||||
final String[] children = dir.list();
|
||||
if (children == null) {
|
||||
System.err.println("AIProfile > can't find AI profile directory!");
|
||||
} else {
|
||||
for (int i = 0; i < children.length; i++) {
|
||||
if (children[i].endsWith(AI_PROFILE_EXT)) {
|
||||
availableProfiles.add(children[i].substring(0, children[i].length() - AI_PROFILE_EXT.length()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return availableProfiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of strings containing all available profiles including
|
||||
* the special "Random" profiles.
|
||||
* @return ArrayList<String> - an array list of strings containing all
|
||||
* available profiles including special random profile tags.
|
||||
*/
|
||||
public static ArrayList<String> getProfilesDisplayList() {
|
||||
final ArrayList<String> availableProfiles = new ArrayList<String>();
|
||||
availableProfiles.add(AI_PROFILE_RANDOM_MATCH);
|
||||
availableProfiles.add(AI_PROFILE_RANDOM_DUEL);
|
||||
availableProfiles.addAll(getAvailableProfiles());
|
||||
|
||||
return availableProfiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random personality from the currently available ones.
|
||||
* @return String - a string containing a random profile from all the
|
||||
* currently available ones.
|
||||
*/
|
||||
public static String getRandomProfile() {
|
||||
return Aggregates.random(getAvailableProfiles());
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple class test facility for AiProfileUtil.
|
||||
*-/
|
||||
public static void selfTest() {
|
||||
final LobbyPlayer activePlayer = Singletons.getControl().getPlayer().getLobbyPlayer();
|
||||
System.out.println(String.format("Current profile = %s", activePlayer.getAiProfile()));
|
||||
ArrayList<String> profiles = getAvailableProfiles();
|
||||
System.out.println(String.format("Available profiles: %s", profiles));
|
||||
if (profiles.size() > 0) {
|
||||
System.out.println(String.format("Loading all profiles..."));
|
||||
loadAllProfiles();
|
||||
System.out.println(String.format("Setting profile %s...", profiles.get(0)));
|
||||
activePlayer.setAiProfile(profiles.get(0));
|
||||
for (AiProps property : AiProps.values()) {
|
||||
System.out.println(String.format("%s = %s", property, getAIProp(activePlayer, property)));
|
||||
}
|
||||
String randomProfile = getRandomProfile();
|
||||
System.out.println(String.format("Loading random profile %s...", randomProfile));
|
||||
activePlayer.setAiProfile(randomProfile);
|
||||
for (AiProps property : AiProps.values()) {
|
||||
System.out.println(String.format("%s = %s", property, getAIProp(activePlayer, property)));
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2013 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai;
|
||||
|
||||
import forge.game.player.LobbyPlayer;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.FileUtil;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Holds default AI personality profile values in an enum.
|
||||
* Loads profile from the given text file when setProfile is called.
|
||||
* If a requested value is not loaded from a profile, default is returned.
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: AIProfile.java 20169 2013-03-08 08:24:17Z Agetian $
|
||||
*/
|
||||
public class AiProfileUtil {
|
||||
private static Map<String, Map<AiProps, String>> loadedProfiles = new HashMap<String, Map<AiProps, String>>();
|
||||
|
||||
private static final String AI_PROFILE_DIR = "res/ai";
|
||||
private static final String AI_PROFILE_EXT = ".ai";
|
||||
|
||||
public static final String AI_PROFILE_RANDOM_MATCH = "* Random (Match) *";
|
||||
public static final String AI_PROFILE_RANDOM_DUEL = "* Random (Duel) *";
|
||||
|
||||
/** Builds an AI profile file name with full relative
|
||||
* path based on the profile name.
|
||||
* @param profileName the name of the profile.
|
||||
* @return the full relative path and file name for the given profile.
|
||||
*/
|
||||
private static String buildFileName(final String profileName) {
|
||||
return String.format("%s/%s%s", AI_PROFILE_DIR, profileName, AI_PROFILE_EXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all profiles
|
||||
*/
|
||||
public static final void loadAllProfiles() {
|
||||
loadedProfiles.clear();
|
||||
ArrayList<String> availableProfiles = getAvailableProfiles();
|
||||
for (String profile : availableProfiles) {
|
||||
loadedProfiles.put(profile, loadProfile(profile));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a single profile.
|
||||
* @param profileName a profile to load.
|
||||
*/
|
||||
private static final Map<AiProps, String> loadProfile(final String profileName) {
|
||||
Map<AiProps, String> profileMap = new HashMap<AiProps, String>();
|
||||
|
||||
List<String> lines = FileUtil.readFile(buildFileName(profileName));
|
||||
for (String line : lines) {
|
||||
|
||||
if (line.startsWith("#") || (line.length() == 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String[] split = line.split("=");
|
||||
|
||||
if (split.length == 2) {
|
||||
profileMap.put(AiProps.valueOf(split[0]), split[1]);
|
||||
} else if (split.length == 1 && line.endsWith("=")) {
|
||||
profileMap.put(AiProps.valueOf(split[0]), "");
|
||||
}
|
||||
}
|
||||
|
||||
return profileMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an AI property value for the current profile.
|
||||
*
|
||||
* @param fp0 an AI property.
|
||||
* @return String
|
||||
*/
|
||||
public static String getAIProp(final LobbyPlayer p, final AiProps fp0) {
|
||||
String val = null;
|
||||
if (!(p instanceof LobbyPlayerAi))
|
||||
return "";
|
||||
String profile = ((LobbyPlayerAi) p).getAiProfile();
|
||||
|
||||
if (loadedProfiles.get(profile) != null) {
|
||||
val = loadedProfiles.get(profile).get(fp0);
|
||||
}
|
||||
if (val == null) { val = fp0.getDefault(); }
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of strings containing all available profiles.
|
||||
* @return ArrayList<String> - an array of strings containing all
|
||||
* available profiles.
|
||||
*/
|
||||
public static ArrayList<String> getAvailableProfiles()
|
||||
{
|
||||
final ArrayList<String> availableProfiles = new ArrayList<String>();
|
||||
|
||||
final File dir = new File(AI_PROFILE_DIR);
|
||||
final String[] children = dir.list();
|
||||
if (children == null) {
|
||||
System.err.println("AIProfile > can't find AI profile directory!");
|
||||
} else {
|
||||
for (int i = 0; i < children.length; i++) {
|
||||
if (children[i].endsWith(AI_PROFILE_EXT)) {
|
||||
availableProfiles.add(children[i].substring(0, children[i].length() - AI_PROFILE_EXT.length()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return availableProfiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of strings containing all available profiles including
|
||||
* the special "Random" profiles.
|
||||
* @return ArrayList<String> - an array list of strings containing all
|
||||
* available profiles including special random profile tags.
|
||||
*/
|
||||
public static ArrayList<String> getProfilesDisplayList() {
|
||||
final ArrayList<String> availableProfiles = new ArrayList<String>();
|
||||
availableProfiles.add(AI_PROFILE_RANDOM_MATCH);
|
||||
availableProfiles.add(AI_PROFILE_RANDOM_DUEL);
|
||||
availableProfiles.addAll(getAvailableProfiles());
|
||||
|
||||
return availableProfiles;
|
||||
}
|
||||
|
||||
public static String[] getProfilesArray() {
|
||||
return getProfilesDisplayList().toArray(ArrayUtils.EMPTY_STRING_ARRAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random personality from the currently available ones.
|
||||
* @return String - a string containing a random profile from all the
|
||||
* currently available ones.
|
||||
*/
|
||||
public static String getRandomProfile() {
|
||||
return Aggregates.random(getAvailableProfiles());
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple class test facility for AiProfileUtil.
|
||||
*-/
|
||||
public static void selfTest() {
|
||||
final LobbyPlayer activePlayer = Singletons.getControl().getPlayer().getLobbyPlayer();
|
||||
System.out.println(String.format("Current profile = %s", activePlayer.getAiProfile()));
|
||||
ArrayList<String> profiles = getAvailableProfiles();
|
||||
System.out.println(String.format("Available profiles: %s", profiles));
|
||||
if (profiles.size() > 0) {
|
||||
System.out.println(String.format("Loading all profiles..."));
|
||||
loadAllProfiles();
|
||||
System.out.println(String.format("Setting profile %s...", profiles.get(0)));
|
||||
activePlayer.setAiProfile(profiles.get(0));
|
||||
for (AiProps property : AiProps.values()) {
|
||||
System.out.println(String.format("%s = %s", property, getAIProp(activePlayer, property)));
|
||||
}
|
||||
String randomProfile = getRandomProfile();
|
||||
System.out.println(String.format("Loading random profile %s...", randomProfile));
|
||||
activePlayer.setAiProfile(randomProfile);
|
||||
for (AiProps property : AiProps.values()) {
|
||||
System.out.println(String.format("%s = %s", property, getAIProp(activePlayer, property)));
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.game.ai;
|
||||
package forge.ai;
|
||||
|
||||
/**
|
||||
* AI personality profile settings identifiers, and their default values.
|
||||
@@ -24,7 +24,12 @@ package forge.game.ai;
|
||||
* from the text file.
|
||||
*/
|
||||
public enum AiProps { /** */
|
||||
AI_MULLIGAN_THRESHOLD ("5"); /** */
|
||||
DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN ("1"), /** */
|
||||
DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE ("3"), /** */
|
||||
DEFAULT_PLANAR_DIE_ROLL_CHANCE ("50"), /** */
|
||||
MULLIGAN_THRESHOLD ("5"), /** */
|
||||
PLANAR_DIE_ROLL_HESITATION_CHANCE ("10"),
|
||||
CHEAT_WITH_MANA_ON_SHUFFLE ("FALSE"); /** */
|
||||
|
||||
private final String strDefaultVal;
|
||||
|
||||
1840
forge-ai/src/main/java/forge/ai/ComputerUtil.java
Normal file
1840
forge-ai/src/main/java/forge/ai/ComputerUtil.java
Normal file
File diff suppressed because it is too large
Load Diff
897
forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
Normal file
897
forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
Normal file
@@ -0,0 +1,897 @@
|
||||
package forge.ai;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.card.CardType;
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.deck.CardPool;
|
||||
import forge.deck.Deck;
|
||||
import forge.deck.DeckSection;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.item.PaperCard;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
import org.apache.commons.lang3.tuple.MutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class ComputerUtilCard {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getMostExpensivePermanentAI.
|
||||
* </p>
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
* @param spell
|
||||
* a {@link forge.game.card.Card} object.
|
||||
* @param targeted
|
||||
* a boolean.
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
public static Card getMostExpensivePermanentAI(final List<Card> list, final SpellAbility spell, final boolean targeted) {
|
||||
List<Card> all = list;
|
||||
if (targeted) {
|
||||
all = CardLists.filter(all, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.canBeTargetedBy(spell);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return ComputerUtilCard.getMostExpensivePermanentAI(all);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sorts a List<Card> by "best" using the EvaluateCreature function.
|
||||
* the best creatures will be first in the list.
|
||||
* </p>
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
*/
|
||||
public static void sortByEvaluateCreature(final List<Card> list) {
|
||||
Collections.sort(list, ComputerUtilCard.EvaluateCreatureComparator);
|
||||
} // sortByEvaluateCreature()
|
||||
|
||||
// The AI doesn't really pick the best artifact, just the most expensive.
|
||||
/**
|
||||
* <p>
|
||||
* getBestArtifactAI.
|
||||
* </p>
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
public static Card getBestArtifactAI(final List<Card> list) {
|
||||
List<Card> all = CardLists.filter(list, CardPredicates.Presets.ARTIFACTS);
|
||||
if (all.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
// get biggest Artifact
|
||||
return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc);
|
||||
}
|
||||
|
||||
// The AI doesn't really pick the best enchantment, just the most expensive.
|
||||
/**
|
||||
* <p>
|
||||
* getBestEnchantmentAI.
|
||||
* </p>
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
* @param spell
|
||||
* a {@link forge.game.card.Card} object.
|
||||
* @param targeted
|
||||
* a boolean.
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
public static Card getBestEnchantmentAI(final List<Card> list, final SpellAbility spell, final boolean targeted) {
|
||||
List<Card> all = CardLists.filter(list, CardPredicates.Presets.ENCHANTMENTS);
|
||||
if (targeted) {
|
||||
all = CardLists.filter(all, new Predicate<Card>() {
|
||||
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.canBeTargetedBy(spell);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// get biggest Enchantment
|
||||
return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getBestLandAI.
|
||||
* </p>
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
public static Card getBestLandAI(final Collection<Card> list) {
|
||||
final List<Card> land = CardLists.filter(list, CardPredicates.Presets.LANDS);
|
||||
if (land.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// prefer to target non basic lands
|
||||
final List<Card> nbLand = CardLists.filter(land, Predicates.not(CardPredicates.Presets.BASIC_LANDS));
|
||||
|
||||
if (!nbLand.isEmpty()) {
|
||||
// TODO - Rank non basics?
|
||||
return Aggregates.random(nbLand);
|
||||
}
|
||||
|
||||
// if no non-basic lands, target the least represented basic land type
|
||||
String sminBL = "";
|
||||
int iminBL = 20000; // hopefully no one will ever have more than 20000
|
||||
// lands of one type....
|
||||
int n = 0;
|
||||
for (String name : MagicColor.Constant.BASIC_LANDS) {
|
||||
n = CardLists.getType(land, name).size();
|
||||
if ((n < iminBL) && (n > 0)) {
|
||||
// if two or more are tied, only the
|
||||
// first
|
||||
// one checked will be used
|
||||
iminBL = n;
|
||||
sminBL = name;
|
||||
}
|
||||
}
|
||||
if (iminBL == 20000) {
|
||||
return null; // no basic land was a minimum
|
||||
}
|
||||
|
||||
final List<Card> bLand = CardLists.getType(land, sminBL);
|
||||
|
||||
for (Card ut : Iterables.filter(bLand, CardPredicates.Presets.UNTAPPED)) {
|
||||
return ut;
|
||||
}
|
||||
|
||||
|
||||
return Aggregates.random(bLand); // random tapped land of least represented type
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getCheapestPermanentAI.
|
||||
* </p>
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
* @param spell
|
||||
* a {@link forge.game.card.Card} object.
|
||||
* @param targeted
|
||||
* a boolean.
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
public static Card getCheapestPermanentAI(Collection<Card> all, final SpellAbility spell, final boolean targeted) {
|
||||
if (targeted) {
|
||||
all = CardLists.filter(all, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.canBeTargetedBy(spell);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (all.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// get cheapest card:
|
||||
Card cheapest = null;
|
||||
|
||||
for (Card c : all) {
|
||||
if (cheapest == null || cheapest.getManaCost().getCMC() <= cheapest.getManaCost().getCMC()) {
|
||||
cheapest = c;
|
||||
}
|
||||
}
|
||||
|
||||
return cheapest;
|
||||
|
||||
}
|
||||
|
||||
// returns null if list.size() == 0
|
||||
/**
|
||||
* <p>
|
||||
* getBestAI.
|
||||
* </p>
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
public static Card getBestAI(final Collection<Card> list) {
|
||||
// Get Best will filter by appropriate getBest list if ALL of the list
|
||||
// is of that type
|
||||
if (Iterables.all(list, CardPredicates.Presets.CREATURES))
|
||||
return ComputerUtilCard.getBestCreatureAI(list);
|
||||
|
||||
if (Iterables.all(list, CardPredicates.Presets.LANDS))
|
||||
return getBestLandAI(list);
|
||||
|
||||
// TODO - Once we get an EvaluatePermanent this should call
|
||||
// getBestPermanent()
|
||||
return ComputerUtilCard.getMostExpensivePermanentAI(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* getBestCreatureAI.
|
||||
*
|
||||
* @param list
|
||||
* the list
|
||||
* @return the card
|
||||
*/
|
||||
public static Card getBestCreatureAI(final Collection<Card> list) {
|
||||
return Aggregates.itemWithMax(Iterables.filter(list, CardPredicates.Presets.CREATURES), ComputerUtilCard.fnEvaluateCreature);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getWorstCreatureAI.
|
||||
* </p>
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
public static Card getWorstCreatureAI(final Collection<Card> list) {
|
||||
return Aggregates.itemWithMin(Iterables.filter(list, CardPredicates.Presets.CREATURES), ComputerUtilCard.fnEvaluateCreature);
|
||||
}
|
||||
|
||||
// This selection rates tokens higher
|
||||
/**
|
||||
* <p>
|
||||
* getBestCreatureToBounceAI.
|
||||
* </p>
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
public static Card getBestCreatureToBounceAI(final List<Card> list) {
|
||||
final int tokenBonus = 60;
|
||||
Card biggest = null;
|
||||
int biggestvalue = -1;
|
||||
|
||||
for(Card card : CardLists.filter(list, CardPredicates.Presets.CREATURES)) {
|
||||
int newvalue = ComputerUtilCard.evaluateCreature(card);
|
||||
newvalue += card.isToken() ? tokenBonus : 0; // raise the value of tokens
|
||||
|
||||
if (biggestvalue < newvalue) {
|
||||
biggest = card;
|
||||
biggestvalue = newvalue;
|
||||
}
|
||||
}
|
||||
return biggest;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getWorstAI.
|
||||
* </p>
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
public static Card getWorstAI(final Collection<Card> list) {
|
||||
return ComputerUtilCard.getWorstPermanentAI(list, false, false, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getWorstPermanentAI.
|
||||
* </p>
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
* @param biasEnch
|
||||
* a boolean.
|
||||
* @param biasLand
|
||||
* a boolean.
|
||||
* @param biasArt
|
||||
* a boolean.
|
||||
* @param biasCreature
|
||||
* a boolean.
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
public static Card getWorstPermanentAI(final Collection<Card> list, final boolean biasEnch, final boolean biasLand,
|
||||
final boolean biasArt, final boolean biasCreature) {
|
||||
if (list.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final boolean hasEnchantmants = Iterables.any(list, CardPredicates.Presets.ENCHANTMENTS);
|
||||
if (biasEnch && hasEnchantmants) {
|
||||
return getCheapestPermanentAI(CardLists.filter(list, CardPredicates.Presets.ENCHANTMENTS), null, false);
|
||||
}
|
||||
|
||||
final boolean hasArtifacts = Iterables.any(list, CardPredicates.Presets.ARTIFACTS);
|
||||
if (biasArt && hasArtifacts) {
|
||||
return getCheapestPermanentAI(CardLists.filter(list, CardPredicates.Presets.ARTIFACTS), null, false);
|
||||
}
|
||||
|
||||
if (biasLand && Iterables.any(list, CardPredicates.Presets.LANDS)) {
|
||||
return ComputerUtilCard.getWorstLand(CardLists.filter(list, CardPredicates.Presets.LANDS));
|
||||
}
|
||||
|
||||
final boolean hasCreatures = Iterables.any(list, CardPredicates.Presets.CREATURES);
|
||||
if (biasCreature && hasCreatures) {
|
||||
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
|
||||
}
|
||||
|
||||
List<Card> lands = CardLists.filter(list, CardPredicates.Presets.LANDS);
|
||||
if (lands.size() > 6) {
|
||||
return ComputerUtilCard.getWorstLand(lands);
|
||||
}
|
||||
|
||||
if (hasEnchantmants || hasArtifacts) {
|
||||
final List<Card> ae = CardLists.filter(list, Predicates.<Card>or(CardPredicates.Presets.ARTIFACTS, CardPredicates.Presets.ENCHANTMENTS));
|
||||
return getCheapestPermanentAI(ae, null, false);
|
||||
}
|
||||
|
||||
if (hasCreatures) {
|
||||
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
|
||||
}
|
||||
|
||||
// Planeswalkers fall through to here, lands will fall through if there
|
||||
// aren't very many
|
||||
return getCheapestPermanentAI(list, null, false);
|
||||
}
|
||||
|
||||
public static final Function<Card, Integer> fnEvaluateCreature = new Function<Card, Integer>() {
|
||||
@Override
|
||||
public Integer apply(Card a) {
|
||||
return ComputerUtilCard.evaluateCreature(a);
|
||||
}
|
||||
};
|
||||
public static final Comparator<Card> EvaluateCreatureComparator = new Comparator<Card>() {
|
||||
@Override
|
||||
public int compare(final Card a, final Card b) {
|
||||
return ComputerUtilCard.evaluateCreature(b) - ComputerUtilCard.evaluateCreature(a);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* <p>
|
||||
* evaluateCreature.
|
||||
* </p>
|
||||
*
|
||||
* @param c
|
||||
* a {@link forge.game.card.Card} object.
|
||||
* @return a int.
|
||||
*/
|
||||
public static int evaluateCreature(final Card c) {
|
||||
|
||||
int value = 100;
|
||||
if (c.isToken()) {
|
||||
value = 80; // tokens should be worth less than actual cards
|
||||
}
|
||||
int power = c.getNetCombatDamage();
|
||||
final int toughness = c.getNetDefense();
|
||||
for (String keyword : c.getKeyword()) {
|
||||
if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.")
|
||||
|| keyword.equals("Prevent all damage that would be dealt by CARDNAME.")
|
||||
|| keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|
||||
|| keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
|
||||
power = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
value += power * 15;
|
||||
value += toughness * 10;
|
||||
value += c.getCMC() * 5;
|
||||
|
||||
// Evasion keywords
|
||||
if (c.hasKeyword("Flying")) {
|
||||
value += power * 10;
|
||||
}
|
||||
if (c.hasKeyword("Horsemanship")) {
|
||||
value += power * 10;
|
||||
}
|
||||
if (c.hasKeyword("Unblockable")) {
|
||||
value += power * 10;
|
||||
} else {
|
||||
if (c.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
|
||||
value += power * 6;
|
||||
}
|
||||
if (c.hasKeyword("Fear")) {
|
||||
value += power * 6;
|
||||
}
|
||||
if (c.hasKeyword("Intimidate")) {
|
||||
value += power * 6;
|
||||
}
|
||||
if (c.hasStartOfKeyword("CantBeBlockedBy")) {
|
||||
value += power * 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Other good keywords
|
||||
if (power > 0) {
|
||||
if (c.hasKeyword("Double Strike")) {
|
||||
value += 10 + (power * 15);
|
||||
} else if (c.hasKeyword("First Strike")) {
|
||||
value += 10 + (power * 5);
|
||||
}
|
||||
if (c.hasKeyword("Deathtouch")) {
|
||||
value += 25;
|
||||
}
|
||||
if (c.hasKeyword("Lifelink")) {
|
||||
value += power * 10;
|
||||
}
|
||||
if (power > 1 && c.hasKeyword("Trample")) {
|
||||
value += (power - 1) * 5;
|
||||
}
|
||||
if (c.hasKeyword("Vigilance")) {
|
||||
value += (power * 5) + (toughness * 5);
|
||||
}
|
||||
if (c.hasKeyword("Wither")) {
|
||||
value += power * 10;
|
||||
}
|
||||
if (c.hasKeyword("Infect")) {
|
||||
value += power * 15;
|
||||
}
|
||||
value += c.getKeywordMagnitude("Rampage");
|
||||
}
|
||||
|
||||
value += c.getKeywordMagnitude("Bushido") * 16;
|
||||
value += c.getAmountOfKeyword("Flanking") * 15;
|
||||
value += c.getAmountOfKeyword("Exalted") * 15;
|
||||
value += c.getKeywordMagnitude("Annihilator") * 50;
|
||||
|
||||
|
||||
// Defensive Keywords
|
||||
if (c.hasKeyword("Reach") && !c.hasKeyword("Flying")) {
|
||||
value += 5;
|
||||
}
|
||||
if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
|
||||
value += 3;
|
||||
}
|
||||
|
||||
// Protection
|
||||
if (c.hasKeyword("Indestructible")) {
|
||||
value += 70;
|
||||
}
|
||||
if (c.hasKeyword("Prevent all damage that would be dealt to CARDNAME.")) {
|
||||
value += 60;
|
||||
} else if (c.hasKeyword("Prevent all combat damage that would be dealt to CARDNAME.")) {
|
||||
value += 50;
|
||||
}
|
||||
if (c.hasKeyword("Hexproof")) {
|
||||
value += 35;
|
||||
} else if (c.hasKeyword("Shroud")) {
|
||||
value += 30;
|
||||
}
|
||||
if (c.hasStartOfKeyword("Protection")) {
|
||||
value += 20;
|
||||
}
|
||||
if (c.hasStartOfKeyword("PreventAllDamageBy")) {
|
||||
value += 10;
|
||||
}
|
||||
value += c.getKeywordMagnitude("Absorb") * 11;
|
||||
|
||||
// Bad keywords
|
||||
if (c.hasKeyword("Defender") || c.hasKeyword("CARDNAME can't attack.")) {
|
||||
value -= (power * 9) + 40;
|
||||
} else if (c.getSVar("SacrificeEndCombat").equals("True")) {
|
||||
value -= 40;
|
||||
}
|
||||
if (c.hasKeyword("CARDNAME can't block.")) {
|
||||
value -= 10;
|
||||
} else if (c.hasKeyword("CARDNAME attacks each turn if able.")) {
|
||||
value -= 10;
|
||||
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
|
||||
value -= 10;
|
||||
} else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
|
||||
value -= toughness * 5;
|
||||
}
|
||||
|
||||
if (c.hasSVar("DestroyWhenDamaged")) {
|
||||
value -= (toughness - 1) * 9;
|
||||
}
|
||||
|
||||
if (c.hasKeyword("CARDNAME can't attack or block.")) {
|
||||
value = 50 + (c.getCMC() * 5); // reset everything - useless
|
||||
}
|
||||
if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
|
||||
if (c.isTapped()) {
|
||||
value = 50 + (c.getCMC() * 5); // reset everything - useless
|
||||
} else {
|
||||
value -= 50;
|
||||
}
|
||||
}
|
||||
if (c.hasKeyword("At the beginning of the end step, destroy CARDNAME.")
|
||||
|| c.hasKeyword("At the beginning of the end step, exile CARDNAME.")
|
||||
|| c.hasKeyword("At the beginning of the end step, sacrifice CARDNAME.")) {
|
||||
value -= 50;
|
||||
} else if (c.hasStartOfKeyword("Cumulative upkeep")) {
|
||||
value -= 30;
|
||||
} else if (c.hasStartOfKeyword("At the beginning of your upkeep, sacrifice CARDNAME unless you pay")) {
|
||||
value -= 20;
|
||||
} else if (c.hasStartOfKeyword("(Echo unpaid)")) {
|
||||
value -= 10;
|
||||
}
|
||||
|
||||
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
|
||||
value -= 20;
|
||||
}
|
||||
if (c.hasStartOfKeyword("Fading")) {
|
||||
value -= 20;
|
||||
}
|
||||
if (c.hasStartOfKeyword("Vanishing")) {
|
||||
value -= 20;
|
||||
}
|
||||
if (c.getSVar("Targeting").equals("Dies")) {
|
||||
value -= 25;
|
||||
}
|
||||
|
||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||
if (sa.isAbility()) {
|
||||
value += 10;
|
||||
}
|
||||
}
|
||||
if (!c.getManaAbility().isEmpty()) {
|
||||
value += 10;
|
||||
}
|
||||
|
||||
if (c.isUntapped()) {
|
||||
value += 1;
|
||||
}
|
||||
|
||||
// paired creatures are more valuable because they grant a bonus to the other creature
|
||||
if (c.isPaired()) {
|
||||
value += 14;
|
||||
}
|
||||
|
||||
if (!c.getEncoded().isEmpty()) {
|
||||
value += 24;
|
||||
}
|
||||
|
||||
return value;
|
||||
|
||||
} // evaluateCreature
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* evaluatePermanentList.
|
||||
* </p>
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
* @return a int.
|
||||
*/
|
||||
public static int evaluatePermanentList(final List<Card> list) {
|
||||
int value = 0;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
value += list.get(i).getCMC() + 1;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* evaluateCreatureList.
|
||||
* </p>
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
* @return a int.
|
||||
*/
|
||||
public static int evaluateCreatureList(final List<Card> list) {
|
||||
return Aggregates.sum(list, fnEvaluateCreature);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* doesCreatureAttackAI.
|
||||
* </p>
|
||||
*
|
||||
* @param ai
|
||||
* the AI player
|
||||
* @param card
|
||||
* a {@link forge.game.card.Card} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
public static boolean doesCreatureAttackAI(final Player ai, final Card card) {
|
||||
AiAttackController aiAtk = new AiAttackController(ai);
|
||||
Combat combat = new Combat(ai);
|
||||
aiAtk.declareAttackers(combat);
|
||||
return combat.isAttacking(card);
|
||||
}
|
||||
|
||||
/**
|
||||
* getMostExpensivePermanentAI.
|
||||
*
|
||||
* @param all
|
||||
* the all
|
||||
* @return the card
|
||||
*/
|
||||
public static Card getMostExpensivePermanentAI(final Collection<Card> all) {
|
||||
Card biggest = null;
|
||||
|
||||
int bigCMC = -1;
|
||||
for (final Card card : all) {
|
||||
int curCMC = card.getCMC();
|
||||
|
||||
// Add all cost of all auras with the same controller
|
||||
final List<Card> auras = CardLists.filterControlledBy(card.getEnchantedBy(), card.getController());
|
||||
curCMC += Aggregates.sum(auras, CardPredicates.Accessors.fnGetCmc) + auras.size();
|
||||
|
||||
if (curCMC >= bigCMC) {
|
||||
bigCMC = curCMC;
|
||||
biggest = card;
|
||||
}
|
||||
}
|
||||
|
||||
return biggest;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getMostProminentCardName.
|
||||
* </p>
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public static String getMostProminentCardName(final List<Card> list) {
|
||||
|
||||
if (list.size() == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
final Map<String, Integer> map = new HashMap<String, Integer>();
|
||||
|
||||
for (final Card c : list) {
|
||||
final String name = c.getName();
|
||||
Integer currentCnt = map.get(name);
|
||||
map.put(name, currentCnt == null ? Integer.valueOf(1) : Integer.valueOf(1 + currentCnt));
|
||||
} // for
|
||||
|
||||
int max = 0;
|
||||
String maxName = "";
|
||||
|
||||
for (final Entry<String, Integer> entry : map.entrySet()) {
|
||||
final String type = entry.getKey();
|
||||
// Log.debug(type + " - " + entry.getValue());
|
||||
|
||||
if (max < entry.getValue()) {
|
||||
max = entry.getValue();
|
||||
maxName = type;
|
||||
}
|
||||
}
|
||||
return maxName;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getMostProminentCreatureType.
|
||||
* </p>
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public static String getMostProminentCreatureType(final List<Card> list) {
|
||||
|
||||
if (list.size() == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
final Map<String, Integer> map = new HashMap<String, Integer>();
|
||||
|
||||
for (final Card c : list) {
|
||||
final ArrayList<String> typeList = c.getType();
|
||||
|
||||
for (final String var : typeList) {
|
||||
if (CardType.isACreatureType(var)) {
|
||||
if (!map.containsKey(var)) {
|
||||
map.put(var, 1);
|
||||
} else {
|
||||
map.put(var, map.get(var) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // for
|
||||
|
||||
int max = 0;
|
||||
String maxType = "";
|
||||
|
||||
for (final Entry<String, Integer> entry : map.entrySet()) {
|
||||
final String type = entry.getKey();
|
||||
// Log.debug(type + " - " + entry.getValue());
|
||||
|
||||
if (max < entry.getValue()) {
|
||||
max = entry.getValue();
|
||||
maxType = type;
|
||||
}
|
||||
}
|
||||
|
||||
return maxType;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getMostProminentColor.
|
||||
* </p>
|
||||
*
|
||||
* @param list
|
||||
* a {@link forge.CardList} object.
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public static String getMostProminentColor(final List<Card> list) {
|
||||
byte colors = CardFactoryUtil.getMostProminentColors(list);
|
||||
for(byte c : MagicColor.WUBRG) {
|
||||
if ( (colors & c) != 0 )
|
||||
return MagicColor.toLongString(c);
|
||||
}
|
||||
return MagicColor.Constant.WHITE; // no difference, there was no prominent color
|
||||
}
|
||||
|
||||
public static String getMostProminentColor(final List<Card> list, final List<String> restrictedToColors) {
|
||||
byte colors = CardFactoryUtil.getMostProminentColorsFromList(list, restrictedToColors);
|
||||
for (byte c : MagicColor.WUBRG) {
|
||||
if ((colors & c) != 0) {
|
||||
return MagicColor.toLongString(c);
|
||||
}
|
||||
}
|
||||
return restrictedToColors.get(0); // no difference, there was no prominent color
|
||||
}
|
||||
|
||||
public static List<String> getColorByProminence(final List<Card> list) {
|
||||
int cntColors = MagicColor.WUBRG.length;
|
||||
final List<Pair<Byte,Integer>> map = new ArrayList<Pair<Byte,Integer>>();
|
||||
for(int i = 0; i < cntColors; i++) {
|
||||
map.add(MutablePair.of(MagicColor.WUBRG[i], 0));
|
||||
}
|
||||
|
||||
for (final Card crd : list) {
|
||||
ColorSet color = CardUtil.getColors(crd);
|
||||
if (color.hasWhite()) map.get(0).setValue(Integer.valueOf(map.get(0).getValue()+1));
|
||||
if (color.hasBlue()) map.get(1).setValue(Integer.valueOf(map.get(1).getValue()+1));
|
||||
if (color.hasBlack()) map.get(2).setValue(Integer.valueOf(map.get(2).getValue()+1));
|
||||
if (color.hasRed()) map.get(3).setValue(Integer.valueOf(map.get(3).getValue()+1));
|
||||
if (color.hasGreen()) map.get(4).setValue(Integer.valueOf(map.get(4).getValue()+1));
|
||||
} // for
|
||||
|
||||
Collections.sort(map, new Comparator<Pair<Byte,Integer>>() {
|
||||
@Override public int compare(Pair<Byte, Integer> o1, Pair<Byte, Integer> o2) {
|
||||
return o2.getValue() - o1.getValue();
|
||||
}
|
||||
});
|
||||
|
||||
// will this part be once dropped?
|
||||
List<String> result = new ArrayList<String>(cntColors);
|
||||
for(Pair<Byte, Integer> idx : map) { // fetch color names in the same order
|
||||
result.add(MagicColor.toLongString(idx.getKey()));
|
||||
}
|
||||
// reverse to get indices for most prominent colors first.
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getWorstLand.
|
||||
* </p>
|
||||
*
|
||||
* @param lands
|
||||
* a {@link forge.CardList} object.
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
public static Card getWorstLand(final List<Card> lands) {
|
||||
Card worstLand = null;
|
||||
int maxScore = 0;
|
||||
// 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;
|
||||
if (score >= maxScore) {
|
||||
worstLand = tmp;
|
||||
maxScore = score;
|
||||
}
|
||||
}
|
||||
return worstLand;
|
||||
} // end getWorstLand
|
||||
|
||||
public static final Predicate<Deck> AI_KNOWS_HOW_TO_PLAY_ALL_CARDS = new Predicate<Deck>() {
|
||||
@Override
|
||||
public boolean apply(Deck d) {
|
||||
for(Entry<DeckSection, CardPool> cp: d) {
|
||||
for(Entry<PaperCard, Integer> e : cp.getValue()) {
|
||||
if ( e.getKey().getRules().getAiHints().getRemAIDecks() )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
public static List<String> chooseColor(SpellAbility sa, int min, int max, List<String> colorChoices) {
|
||||
List<String> chosen = new ArrayList<String>();
|
||||
Player ai = sa.getActivatingPlayer();
|
||||
final Game game = ai.getGame();
|
||||
Player opp = ai.getOpponent();
|
||||
if (sa.hasParam("AILogic")) {
|
||||
final String logic = sa.getParam("AILogic");
|
||||
if (logic.equals("MostProminentInHumanDeck")) {
|
||||
chosen.add(ComputerUtilCard.getMostProminentColor(CardLists.filterControlledBy(game.getCardsInGame(), opp), colorChoices));
|
||||
} else if (logic.equals("MostProminentInComputerDeck")) {
|
||||
chosen.add(ComputerUtilCard.getMostProminentColor(CardLists.filterControlledBy(game.getCardsInGame(), ai), colorChoices));
|
||||
} else if (logic.equals("MostProminentDualInComputerDeck")) {
|
||||
List<String> prominence = ComputerUtilCard.getColorByProminence(CardLists.filterControlledBy(game.getCardsInGame(), ai));
|
||||
chosen.add(prominence.get(0));
|
||||
chosen.add(prominence.get(1));
|
||||
}
|
||||
else if (logic.equals("MostProminentInGame")) {
|
||||
chosen.add(ComputerUtilCard.getMostProminentColor(game.getCardsInGame(), colorChoices));
|
||||
}
|
||||
else if (logic.equals("MostProminentHumanCreatures")) {
|
||||
List<Card> list = opp.getCreaturesInPlay();
|
||||
if (list.isEmpty()) {
|
||||
list = CardLists.filter(CardLists.filterControlledBy(game.getCardsInGame(), opp), CardPredicates.Presets.CREATURES);
|
||||
}
|
||||
chosen.add(ComputerUtilCard.getMostProminentColor(list, colorChoices));
|
||||
}
|
||||
else if (logic.equals("MostProminentComputerControls")) {
|
||||
chosen.add(ComputerUtilCard.getMostProminentColor(ai.getCardsIn(ZoneType.Battlefield), colorChoices));
|
||||
}
|
||||
else if (logic.equals("MostProminentHumanControls")) {
|
||||
chosen.add(ComputerUtilCard.getMostProminentColor(ai.getOpponent().getCardsIn(ZoneType.Battlefield), colorChoices));
|
||||
}
|
||||
else if (logic.equals("MostProminentPermanent")) {
|
||||
final List<Card> list = game.getCardsIn(ZoneType.Battlefield);
|
||||
chosen.add(ComputerUtilCard.getMostProminentColor(list, colorChoices));
|
||||
}
|
||||
else if (logic.equals("MostProminentAttackers") && game.getPhaseHandler().inCombat()) {
|
||||
chosen.add(ComputerUtilCard.getMostProminentColor(game.getCombat().getAttackers(), colorChoices));
|
||||
}
|
||||
else if (logic.equals("MostProminentInActivePlayerHand")) {
|
||||
chosen.add(ComputerUtilCard.getMostProminentColor(game.getPhaseHandler().getPlayerTurn().getCardsIn(ZoneType.Hand), colorChoices));
|
||||
}
|
||||
else if (logic.equals("MostProminentKeywordInComputerDeck")) {
|
||||
List<Card> list = ai.getAllCards();
|
||||
int m1 = 0;
|
||||
String chosenColor = MagicColor.Constant.WHITE;
|
||||
|
||||
for (final String c : MagicColor.Constant.ONLY_COLORS) {
|
||||
final int cmp = CardLists.filter(list, CardPredicates.containsKeyword(c)).size();
|
||||
if (cmp > m1) {
|
||||
m1 = cmp;
|
||||
chosenColor = c;
|
||||
}
|
||||
}
|
||||
chosen.add(chosenColor);
|
||||
}
|
||||
}
|
||||
if (chosen.size() == 0) {
|
||||
chosen.add(MagicColor.Constant.GREEN);
|
||||
}
|
||||
return chosen;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,30 +1,20 @@
|
||||
package forge.game.ai;
|
||||
package forge.ai;
|
||||
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.*;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.Spell;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.TextUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import forge.Card;
|
||||
import forge.CardLists;
|
||||
import forge.CounterType;
|
||||
import forge.card.ability.AbilityUtils;
|
||||
import forge.card.cost.Cost;
|
||||
import forge.card.cost.CostDamage;
|
||||
import forge.card.cost.CostDiscard;
|
||||
import forge.card.cost.CostPart;
|
||||
import forge.card.cost.CostPayLife;
|
||||
import forge.card.cost.CostPayment;
|
||||
import forge.card.cost.CostPutCounter;
|
||||
import forge.card.cost.CostRemoveCounter;
|
||||
import forge.card.cost.CostSacrifice;
|
||||
import forge.card.spellability.Spell;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
@@ -70,21 +60,11 @@ public class ComputerUtilCost {
|
||||
if (cost == null) {
|
||||
return true;
|
||||
}
|
||||
double p1p1Percent = .25;
|
||||
if (source.isCreature()) {
|
||||
p1p1Percent = .1;
|
||||
}
|
||||
final double otherPercent = .9;
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
|
||||
|
||||
// A card has a 25% chance per counter to be able to pass
|
||||
// through here
|
||||
// 4+ counters will always pass. 0 counters will never
|
||||
final CounterType type = remCounter.getCounter();
|
||||
final double percent = type.name().equals("P1P1") ? p1p1Percent : otherPercent;
|
||||
final int currentNum = source.getCounters(type);
|
||||
final CounterType type = remCounter.counter;
|
||||
if (!part.payCostFromSource()) {
|
||||
if (type.name().equals("P1P1")) {
|
||||
return false;
|
||||
@@ -96,15 +76,6 @@ public class ComputerUtilCost {
|
||||
if (type.name().equals("P1P1") && source.getLethalDamage() <= 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Integer amount = part.convertAmount();
|
||||
if (amount == null) {
|
||||
amount = currentNum;
|
||||
}
|
||||
final double chance = percent * (currentNum / amount);
|
||||
if (chance <= MyRandom.getRandom().nextFloat()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -307,7 +278,7 @@ public class ComputerUtilCost {
|
||||
* </p>
|
||||
*
|
||||
* @param hostCard
|
||||
* a {@link forge.Card} object.
|
||||
* a {@link forge.game.card.Card} object.
|
||||
* @param costString
|
||||
* a {@link java.lang.String} object.
|
||||
* @return a boolean.
|
||||
@@ -335,7 +306,7 @@ public class ComputerUtilCost {
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.card.spellability.SpellAbility} object.
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param player
|
||||
* a {@link forge.game.player.Player} object.
|
||||
* @return a boolean.
|
||||
@@ -378,7 +349,7 @@ public class ComputerUtilCost {
|
||||
} // canPayCost()
|
||||
|
||||
public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, List<Player> payers) {
|
||||
final Card source = sa.getSourceCard();
|
||||
final Card source = sa.getHostCard();
|
||||
boolean payForOwnOnly = "OnlyOwn".equals(sa.getParam("UnlessAI"));
|
||||
boolean payOwner = sa.hasParam("UnlessAI") ? sa.getParam("UnlessAI").startsWith("Defined") : false;
|
||||
boolean payNever = "Never".equals(sa.getParam("UnlessAI"));
|
||||
@@ -395,7 +366,8 @@ public class ComputerUtilCost {
|
||||
}
|
||||
} else if (shockland) {
|
||||
if (payer.getLife() > 3 && payer.canPayLife(2)) {
|
||||
final int landsize = payer.getLandsInPlay().size();
|
||||
// If the new land size would equal the CMC of a card in AIs hand, play it untapped
|
||||
final int landsize = payer.getLandsInPlay().size() + 1;
|
||||
for (Card c : payer.getCardsIn(ZoneType.Hand)) {
|
||||
if (landsize == c.getCMC()) {
|
||||
return true;
|
||||
@@ -414,6 +386,10 @@ public class ComputerUtilCost {
|
||||
if (payerCreatures > sourceCreatures + 1) {
|
||||
return false;
|
||||
}
|
||||
} else if ("LifeLE2".equals(sa.getParam("UnlessAI"))) {
|
||||
if (payer.getLife() < 3) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// AI will only pay when it's not already payed and only opponents abilities
|
||||
911
forge-ai/src/main/java/forge/ai/ComputerUtilMana.java
Normal file
911
forge-ai/src/main/java/forge/ai/ComputerUtilMana.java
Normal file
@@ -0,0 +1,911 @@
|
||||
package forge.ai;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaAtom;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostShard;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameActionUtil;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPartMana;
|
||||
import forge.game.cost.CostPayment;
|
||||
import forge.game.mana.Mana;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.mana.ManaPool;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.spellability.AbilityManaPart;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class ComputerUtilMana {
|
||||
private final static boolean DEBUG_MANA_PAYMENT = false;
|
||||
|
||||
public static boolean canPayManaCost(ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) {
|
||||
cost = new ManaCostBeingPaid(cost); //check copy of cost so it doesn't modify the exist cost being paid
|
||||
return payManaCost(cost, sa, ai, true, 0, true);
|
||||
}
|
||||
|
||||
public static boolean payManaCost(ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) {
|
||||
return payManaCost(cost, sa, ai, false, 0, true);
|
||||
}
|
||||
|
||||
public static boolean canPayManaCost(final SpellAbility sa, final Player ai, final int extraMana) {
|
||||
return payManaCost(sa, ai, true, extraMana, true);
|
||||
}
|
||||
|
||||
// Does not check if mana sources can be used right now, just checks for potential chance.
|
||||
public static boolean hasEnoughManaSourcesToCast(final SpellAbility sa, final Player ai) {
|
||||
sa.setActivatingPlayer(ai);
|
||||
return payManaCost(sa, ai, true, 0, false);
|
||||
}
|
||||
|
||||
public static boolean payManaCost(final Player ai, final SpellAbility sa) {
|
||||
return payManaCost(sa, ai, false, 0, true);
|
||||
}
|
||||
|
||||
private static void refundMana(List<Mana> manaSpent, Player ai, SpellAbility sa) {
|
||||
if (sa.getHostCard() != null) {
|
||||
sa.getHostCard().setCanCounter(true);
|
||||
}
|
||||
for (final Mana m : manaSpent) {
|
||||
ai.getManaPool().addMana(m);
|
||||
}
|
||||
manaSpent.clear();
|
||||
}
|
||||
|
||||
|
||||
private static boolean payManaCost(final SpellAbility sa, final Player ai, final boolean test, final int extraMana, boolean checkPlayable) {
|
||||
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, test, extraMana);
|
||||
return payManaCost(cost, sa, ai, test, extraMana, checkPlayable);
|
||||
}
|
||||
|
||||
private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, final int extraMana, boolean checkPlayable) {
|
||||
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
|
||||
List<Mana> manaSpentToPay = test ? new ArrayList<Mana>() : sa.getPayingMana();
|
||||
|
||||
final ManaPool manapool = ai.getManaPool();
|
||||
List<ManaCostShard> unpaidShards = cost.getUnpaidShards();
|
||||
Collections.sort(unpaidShards); // most difficult shards must come first
|
||||
for (ManaCostShard part : unpaidShards) {
|
||||
if (part != ManaCostShard.X) {
|
||||
if (cost.isPaid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get a mana of this type from floating, bail if none available
|
||||
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction());
|
||||
if (mana == null) {
|
||||
continue; // no matching mana in the pool
|
||||
}
|
||||
else {
|
||||
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana)) {
|
||||
manaSpentToPay.add(0, mana);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cost.isPaid()) {
|
||||
// refund any mana taken from mana pool when test
|
||||
if(test)
|
||||
refundMana(manaSpentToPay, ai, sa);
|
||||
|
||||
handleOfferingsAI(sa, test, cost.isPaid());
|
||||
return true;
|
||||
}
|
||||
|
||||
// arrange all mana abilities by color produced.
|
||||
final Multimap<Integer, SpellAbility> manaAbilityMap = ComputerUtilMana.groupSourcesByManaColor(ai, checkPlayable);
|
||||
if (manaAbilityMap.isEmpty()) {
|
||||
refundMana(manaSpentToPay, ai, sa);
|
||||
|
||||
handleOfferingsAI(sa, test, cost.isPaid());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DEBUG_MANA_PAYMENT) {
|
||||
System.out.println("DEBUG_MANA_PAYMENT: manaAbilityMap = " + manaAbilityMap);
|
||||
}
|
||||
|
||||
// select which abilities may be used for each shard
|
||||
Multimap<ManaCostShard, SpellAbility> sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost);
|
||||
|
||||
// if (DEBUG_MANA_PAYMENT) {
|
||||
// System.out.println((test ? "test -- " : "PROD -- ") + FThreads.debugGetStackTraceItem(5, true));
|
||||
// for (Entry<ManaCostShard, Collection<SpellAbility>> src : sourcesForShards.entrySet()) {
|
||||
// System.out.println("\t" +src.getKey() + " : " + src.getValue().size() + " source(s)");
|
||||
// for (SpellAbility sss : src.getValue()) {
|
||||
// System.out.printf("\t\t%s - %s%n", sss.getHostCard(), sss);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
List<String> paymentPlan = new ArrayList<String>();
|
||||
|
||||
ManaCostShard toPay = null;
|
||||
// Loop over mana needed
|
||||
while (!cost.isPaid()) {
|
||||
toPay = getNextShardToPay(cost, sourcesForShards);
|
||||
|
||||
Collection<SpellAbility> saList = sourcesForShards.get(toPay);
|
||||
SpellAbility saPayment = null;
|
||||
if (saList != null) {
|
||||
for (final SpellAbility ma : saList) {
|
||||
if (ma.getHostCard() == sa.getHostCard()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String typeRes = cost.getSourceRestriction();
|
||||
if (StringUtils.isNotBlank(typeRes) && !ma.getHostCard().isType(typeRes)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (canPayShardWithSpellAbility(toPay, ai, ma, sa, checkPlayable || !test)) {
|
||||
saPayment = ma;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if (DEBUG_MANA_PAYMENT) {
|
||||
paymentPlan.add(String.format("%s : (%s) %s", toPay, saPayment == null ? "LIFE" : saPayment.getHostCard(), saPayment));
|
||||
}
|
||||
|
||||
if (saPayment == null) {
|
||||
if (!toPay.isPhyrexian() || !ai.canPayLife(2)) {
|
||||
break; // cannot pay
|
||||
}
|
||||
|
||||
cost.payPhyrexian();
|
||||
if (!test) {
|
||||
ai.payLife(2, sa.getHostCard());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
setExpressColorChoice(sa, ai, cost, toPay, saPayment);
|
||||
|
||||
if (test) {
|
||||
String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment);
|
||||
manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced);
|
||||
//System.out.println(manaProduced);
|
||||
payMultipleMana(cost, manaProduced, ai);
|
||||
|
||||
// remove from available lists
|
||||
Iterator<SpellAbility> itSa = sourcesForShards.values().iterator();
|
||||
while (itSa.hasNext()) {
|
||||
SpellAbility srcSa = itSa.next();
|
||||
if (srcSa.getHostCard().equals(saPayment.getHostCard())) {
|
||||
itSa.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (saPayment.getPayCosts() != null) {
|
||||
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
|
||||
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment))) {
|
||||
saList.remove(saPayment);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.err.println("Ability " + saPayment + " from " + saPayment.getHostCard() + " had NULL as payCost");
|
||||
saPayment.getHostCard().tap();
|
||||
}
|
||||
|
||||
ai.getGame().getStack().addAndUnfreeze(saPayment);
|
||||
// subtract mana from mana pool
|
||||
manapool.payManaFromAbility(sa, cost, saPayment);
|
||||
|
||||
// no need to remove abilities from resource map,
|
||||
// once their costs are paid and consume resources, they can not be used again
|
||||
}
|
||||
}
|
||||
|
||||
handleOfferingsAI(sa, test, cost.isPaid());
|
||||
|
||||
// if (DEBUG_MANA_PAYMENT) {
|
||||
// System.err.printf("%s > [%s] payment has %s (%s +%d) for (%s) %s:%n\t%s%n%n",
|
||||
// FThreads.debugGetCurrThreadId(), test ? "test" : "PROD", cost.isPaid() ? "*PAID*" : "failed", originalCost,
|
||||
// extraMana, sa.getHostCard(), sa.toUnsuppressedString(), StringUtils.join(paymentPlan, "\n\t"));
|
||||
// }
|
||||
|
||||
if (!cost.isPaid()) {
|
||||
refundMana(manaSpentToPay, ai, sa);
|
||||
if (test) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
System.out.println("ComputerUtil : payManaCost() cost was not paid for " + sa.getHostCard().getName() + ". Didn't find what to pay for " + toPay);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (test)
|
||||
refundMana(manaSpentToPay, ai, sa);
|
||||
|
||||
sa.getHostCard().setColorsPaid(cost.getColorsPaid());
|
||||
// if (sa instanceof Spell_Permanent) // should probably add this
|
||||
sa.getHostCard().setSunburstValue(cost.getSunburst());
|
||||
return true;
|
||||
} // payManaCost()
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getManaFrom.
|
||||
* </p>
|
||||
*
|
||||
* @param saBeingPaidFor
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @return a {@link forge.game.mana.Mana} object.
|
||||
*/
|
||||
private static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor, String restriction) {
|
||||
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard, saBeingPaidFor, restriction);
|
||||
|
||||
// Exclude border case
|
||||
if (weightedOptions.isEmpty()) {
|
||||
return null; // There is no matching mana in the pool
|
||||
}
|
||||
|
||||
// select equal weight possibilities
|
||||
List<Mana> manaChoices = new ArrayList<Mana>();
|
||||
int bestWeight = Integer.MIN_VALUE;
|
||||
for (Pair<Mana, Integer> option : weightedOptions) {
|
||||
int thisWeight = option.getRight();
|
||||
Mana thisMana = option.getLeft();
|
||||
|
||||
if (thisWeight > bestWeight) {
|
||||
manaChoices.clear();
|
||||
bestWeight = thisWeight;
|
||||
}
|
||||
|
||||
if (thisWeight == bestWeight) {
|
||||
// add only distinct Mana-s
|
||||
boolean haveDuplicate = false;
|
||||
for (Mana m : manaChoices) {
|
||||
if (m.equals(thisMana)) {
|
||||
haveDuplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!haveDuplicate) {
|
||||
manaChoices.add(thisMana);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// got an only one best option?
|
||||
if (manaChoices.size() == 1) {
|
||||
return manaChoices.get(0);
|
||||
}
|
||||
|
||||
// Let them choose then
|
||||
return ai.getController().chooseManaFromPool(manaChoices);
|
||||
}
|
||||
|
||||
private static List<Pair<Mana, Integer>> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard, final SpellAbility saBeingPaidFor, String restriction) {
|
||||
final List<Pair<Mana, Integer>> weightedOptions = new ArrayList<Pair<Mana, Integer>>();
|
||||
for (final Mana thisMana : manapool) {
|
||||
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (thisMana.getManaAbility() != null && !thisMana.getManaAbility().meetsManaRestrictions(saBeingPaidFor)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean canPay = manapool.canPayForShardWithColor(shard, thisMana.getColor());
|
||||
if (!canPay || (shard.isSnow() && !thisMana.isSnow())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(restriction) && !thisMana.getSourceCard().isType(restriction)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// prefer colorless mana to spend
|
||||
int weight = thisMana.isColorless() ? 5 : 0;
|
||||
|
||||
// prefer restricted mana to spend
|
||||
if (thisMana.isRestricted()) {
|
||||
weight += 2;
|
||||
}
|
||||
|
||||
// Spend non-snow mana first
|
||||
if (!thisMana.isSnow()) {
|
||||
weight += 1;
|
||||
}
|
||||
|
||||
weightedOptions.add(Pair.of(thisMana, weight));
|
||||
}
|
||||
return weightedOptions;
|
||||
}
|
||||
|
||||
private static void setExpressColorChoice(final SpellAbility sa, final Player ai, ManaCostBeingPaid cost,
|
||||
ManaCostShard toPay, SpellAbility saPayment) {
|
||||
|
||||
AbilityManaPart m = saPayment.getManaPart();
|
||||
if (m.isComboMana())
|
||||
getComboManaChoice(ai, saPayment, sa, cost);
|
||||
else if (saPayment.getApi() == ApiType.ManaReflected) {
|
||||
System.out.println("Evaluate reflected mana of: " + saPayment.getHostCard());
|
||||
Set<String> reflected = CardUtil.getReflectableManaColors(saPayment);
|
||||
|
||||
for (byte c : MagicColor.WUBRG) {
|
||||
if (ai.getManaPool().canPayForShardWithColor(toPay, c) && reflected.contains(MagicColor.toLongString(c))) {
|
||||
m.setExpressChoice(MagicColor.toShortString(c));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (m.isAnyMana()) {
|
||||
byte colorChoice = 0;
|
||||
if (toPay.isOr2Colorless())
|
||||
colorChoice = toPay.getColorMask();
|
||||
else {
|
||||
for (byte c : MagicColor.WUBRG) {
|
||||
if (ai.getManaPool().canPayForShardWithColor(toPay, c)) {
|
||||
colorChoice = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
m.setExpressChoice(MagicColor.toShortString(colorChoice));
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts) {
|
||||
final Card sourceCard = ma.getHostCard();
|
||||
|
||||
if (toPay.isSnow() && !sourceCard.isSnow()) { return false; }
|
||||
|
||||
AbilityManaPart m = ma.getManaPart();
|
||||
if (!m.meetsManaRestrictions(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkCosts) {
|
||||
// Check if AI can still play this mana ability
|
||||
ma.setActivatingPlayer(ai);
|
||||
if (ma.getPayCosts() != null) { // if the AI can't pay the additional costs skip the mana ability
|
||||
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) {
|
||||
return false;
|
||||
}
|
||||
} else if (sourceCard.isTapped()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m.isComboMana()) {
|
||||
for (String s : m.getComboColors().split(" ")) {
|
||||
if ("Any".equals(s) || ai.getManaPool().canPayForShardWithColor(toPay, MagicColor.fromName(s)))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
} else if (ma.getApi() == ApiType.ManaReflected) {
|
||||
Set<String> reflected = CardUtil.getReflectableManaColors(ma);
|
||||
|
||||
for (byte c : MagicColor.WUBRG) {
|
||||
if (ai.getManaPool().canPayForShardWithColor(toPay, c) && reflected.contains(MagicColor.toLongString(c))) {
|
||||
m.setExpressChoice(MagicColor.toShortString(c));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private static ManaCostShard getNextShardToPay(ManaCostBeingPaid cost, Multimap<ManaCostShard, SpellAbility> sourcesForShards) {
|
||||
// mind the priorities
|
||||
// * Pay mono-colored first,
|
||||
// * Pay 2/C with matching colors
|
||||
// * pay hybrids
|
||||
// * pay phyrexian, keep mana for colorless
|
||||
// * pay colorless
|
||||
|
||||
for (ManaCostShard s : cost.getDistinctShards()) { // should check in which order EnumMap enumerates keys. If it's same as enum member declaration, nothing else needs to be done.
|
||||
return s;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void adjustManaCostToAvoidNegEffects(ManaCostBeingPaid cost, final Card card, Player ai) {
|
||||
// Make mana needed to avoid negative effect a mandatory cost for the AI
|
||||
for (String manaPart : card.getSVar("ManaNeededToAvoidNegativeEffect").split(",")) {
|
||||
// convert long color strings to short color strings
|
||||
byte mask = MagicColor.fromName(manaPart);
|
||||
|
||||
// make mana mandatory for AI
|
||||
if (!cost.needsColor(mask, ai.getManaPool()) && cost.getColorlessManaAmount() > 0) {
|
||||
ManaCostShard shard = ManaCostShard.valueOf(mask);
|
||||
cost.increaseShard(shard, 1);
|
||||
cost.decreaseColorlessMana(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getComboManaChoice.
|
||||
* </p>
|
||||
*
|
||||
* @param abMana
|
||||
* a {@link forge.card.spellability.AbilityMana} object.
|
||||
* @param saRoot
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param cost
|
||||
* a {@link forge.game.mana.ManaCostBeingPaid} object.
|
||||
* @return String
|
||||
*/
|
||||
private static void getComboManaChoice(final Player ai, final SpellAbility manaAb, final SpellAbility saRoot, final ManaCostBeingPaid cost) {
|
||||
final StringBuilder choiceString = new StringBuilder();
|
||||
final Card source = manaAb.getHostCard();
|
||||
final AbilityManaPart abMana = manaAb.getManaPart();
|
||||
|
||||
if (abMana.isComboMana()) {
|
||||
int amount = manaAb.hasParam("Amount") ? AbilityUtils.calculateAmount(source, manaAb.getParam("Amount"), saRoot) : 1;
|
||||
final ManaCostBeingPaid testCost = new ManaCostBeingPaid(cost);
|
||||
final String[] comboColors = abMana.getComboColors().split(" ");
|
||||
for (int nMana = 1; nMana <= amount; nMana++) {
|
||||
String choice = "";
|
||||
// Use expressChoice first
|
||||
if (!abMana.getExpressChoice().isEmpty()) {
|
||||
choice = abMana.getExpressChoice();
|
||||
abMana.clearExpressChoice();
|
||||
byte colorMask = MagicColor.fromName(choice);
|
||||
if (abMana.canProduce(choice, manaAb) && testCost.isAnyPartPayableWith(colorMask, ai.getManaPool())) {
|
||||
choiceString.append(choice);
|
||||
payMultipleMana(testCost, choice, ai);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// check colors needed for cost
|
||||
if (!testCost.isPaid()) {
|
||||
// Loop over combo colors
|
||||
for (String color : comboColors) {
|
||||
if (testCost.isAnyPartPayableWith(MagicColor.fromName(color), ai.getManaPool())) {
|
||||
payMultipleMana(testCost, color, ai);
|
||||
if (nMana != 1) {
|
||||
choiceString.append(" ");
|
||||
}
|
||||
choiceString.append(color);
|
||||
choice = color;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!choice.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// check if combo mana can produce most common color in hand
|
||||
String commonColor = ComputerUtilCard.getMostProminentColor(ai.getCardsIn(
|
||||
ZoneType.Hand));
|
||||
if (!commonColor.isEmpty() && abMana.getComboColors().contains(MagicColor.toShortString(commonColor))) {
|
||||
choice = MagicColor.toShortString(commonColor);
|
||||
}
|
||||
else {
|
||||
// default to first color
|
||||
choice = comboColors[0];
|
||||
}
|
||||
if (nMana != 1) {
|
||||
choiceString.append(" ");
|
||||
}
|
||||
choiceString.append(choice);
|
||||
}
|
||||
}
|
||||
if (choiceString.toString().isEmpty()) {
|
||||
choiceString.append("0");
|
||||
}
|
||||
|
||||
abMana.setExpressChoice(choiceString.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* payMultipleMana.
|
||||
* </p>
|
||||
* @param testCost
|
||||
*
|
||||
* @param mana
|
||||
* a {@link java.lang.String} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private final static String payMultipleMana(ManaCostBeingPaid testCost, String mana, final Player p) {
|
||||
List<String> unused = new ArrayList<String>(4);
|
||||
for (String manaPart : TextUtil.split(mana, ' ')) {
|
||||
if (StringUtils.isNumeric(manaPart)) {
|
||||
for (int i = Integer.parseInt(manaPart); i > 0; i--) {
|
||||
boolean wasNeeded = testCost.ai_payMana("1", p.getManaPool());
|
||||
if (!wasNeeded) {
|
||||
unused.add(Integer.toString(i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
String color = MagicColor.toShortString(manaPart);
|
||||
boolean wasNeeded = testCost.ai_payMana(color, p.getManaPool());
|
||||
if (!wasNeeded) {
|
||||
unused.add(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
return unused.isEmpty() ? null : StringUtils.join(unused, ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all mana sources.
|
||||
* @param manaAbilityMap
|
||||
* @param partSources
|
||||
* @param partPriority
|
||||
* @param costParts
|
||||
* @param foundAllSources
|
||||
* @return Were all mana sources found?
|
||||
*/
|
||||
private static Multimap<ManaCostShard, SpellAbility> groupAndOrderToPayShards(final Player ai, final Multimap<Integer, SpellAbility> manaAbilityMap,
|
||||
final ManaCostBeingPaid cost) {
|
||||
Multimap<ManaCostShard, SpellAbility> res = ArrayListMultimap.create();
|
||||
|
||||
// loop over cost parts
|
||||
for (ManaCostShard shard : cost.getDistinctShards()) {
|
||||
if (shard == ManaCostShard.S) {
|
||||
res.putAll(shard, manaAbilityMap.get(ManaAtom.IS_SNOW));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shard.isOr2Colorless()) {
|
||||
Integer colorKey = Integer.valueOf(shard.getColorMask());
|
||||
if (manaAbilityMap.containsKey(colorKey))
|
||||
res.putAll(shard, manaAbilityMap.get(colorKey));
|
||||
if (manaAbilityMap.containsKey(ManaAtom.COLORLESS))
|
||||
res.putAll(shard, manaAbilityMap.get(ManaAtom.COLORLESS));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Entry<Integer, SpellAbility> kv : manaAbilityMap.entries()) {
|
||||
// apply mana color change matrix here
|
||||
if (ai.getManaPool().canPayForShardWithColor(shard, kv.getKey().byteValue())) {
|
||||
res.put(shard, kv.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cost.getColorlessManaAmount() > 0 && manaAbilityMap.containsKey(ManaAtom.COLORLESS)) {
|
||||
res.putAll(ManaCostShard.COLORLESS, manaAbilityMap.get(ManaAtom.COLORLESS));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the ManaCost for the given SpellAbility.
|
||||
* @param sa
|
||||
* @param test
|
||||
* @param extraMana
|
||||
* @return ManaCost
|
||||
*/
|
||||
static ManaCostBeingPaid calculateManaCost(final SpellAbility sa, final boolean test, final int extraMana) {
|
||||
ZoneType castFromBackup = null;
|
||||
if (test && sa.isSpell()) {
|
||||
castFromBackup = sa.getHostCard().getCastFrom();
|
||||
sa.getHostCard().setCastFrom(sa.getHostCard().getZone().getZoneType());
|
||||
}
|
||||
|
||||
Cost payCosts = sa.getPayCosts();
|
||||
CostPartMana manapart = payCosts != null ? payCosts.getCostMana() : null;
|
||||
final ManaCost mana = payCosts != null ? ( manapart == null ? ManaCost.ZERO : manapart.getManaCostFor(sa) ) : ManaCost.NO_COST;
|
||||
|
||||
String restriction = null;
|
||||
if (payCosts != null && payCosts.getCostMana() != null) {
|
||||
restriction = payCosts.getCostMana().getRestiction();
|
||||
}
|
||||
ManaCostBeingPaid cost = new ManaCostBeingPaid(mana, restriction);
|
||||
cost.applySpellCostChange(sa, test);
|
||||
|
||||
final Card card = sa.getHostCard();
|
||||
// Tack xMana Payments into mana here if X is a set value
|
||||
if ((sa.getPayCosts() != null) && (cost.getXcounter() > 0 || extraMana > 0)) {
|
||||
int manaToAdd = 0;
|
||||
if (test && extraMana > 0) {
|
||||
final int multiplicator = Math.max(cost.getXcounter(), 1);
|
||||
manaToAdd = extraMana * multiplicator;
|
||||
} else {
|
||||
// For Count$xPaid set PayX in the AFs then use that here
|
||||
// Else calculate it as appropriate.
|
||||
final String xSvar = card.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X";
|
||||
if (!card.getSVar(xSvar).equals("")) {
|
||||
if (xSvar.equals("PayX")) {
|
||||
manaToAdd = Integer.parseInt(card.getSVar(xSvar)) * cost.getXcounter(); // X
|
||||
} else {
|
||||
manaToAdd = AbilityUtils.calculateAmount(card, xSvar, sa) * cost.getXcounter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String manaXColor = sa.getParam("XColor");
|
||||
ManaCostShard shardToGrow = ManaCostShard.parseNonGeneric(manaXColor == null ? "1" : manaXColor);
|
||||
cost.increaseShard(shardToGrow, manaToAdd);
|
||||
|
||||
if (!test) {
|
||||
card.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
||||
}
|
||||
}
|
||||
|
||||
if (test && sa.isSpell()) {
|
||||
sa.getHostCard().setCastFrom(castFromBackup);
|
||||
}
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
//This method is currently used by AI to estimate available mana
|
||||
public static List<Card> getAvailableMana(final Player ai, final boolean checkPlayable) {
|
||||
final List<Card> list = ai.getCardsIn(ZoneType.Battlefield);
|
||||
list.addAll(ai.getCardsIn(ZoneType.Hand));
|
||||
final List<Card> manaSources = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
for (final SpellAbility am : getAIPlayableMana(c)) {
|
||||
am.setActivatingPlayer(ai);
|
||||
if (!checkPlayable || am.canPlay()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}); // CardListFilter
|
||||
|
||||
final List<Card> sortedManaSources = new ArrayList<Card>();
|
||||
final List<Card> otherManaSources = new ArrayList<Card>();
|
||||
final List<Card> colorlessManaSources = new ArrayList<Card>();
|
||||
final List<Card> oneManaSources = new ArrayList<Card>();
|
||||
final List<Card> twoManaSources = new ArrayList<Card>();
|
||||
final List<Card> threeManaSources = new ArrayList<Card>();
|
||||
final List<Card> fourManaSources = new ArrayList<Card>();
|
||||
final List<Card> fiveManaSources = new ArrayList<Card>();
|
||||
final List<Card> anyColorManaSources = new ArrayList<Card>();
|
||||
|
||||
// Sort mana sources
|
||||
// 1. Use lands that can only produce colorless mana without
|
||||
// drawback/cost first
|
||||
// 2. Search for mana sources that have a certain number of abilities
|
||||
// 3. Use lands that produce any color many
|
||||
// 4. all other sources (creature, costs, drawback, etc.)
|
||||
for (Card card : manaSources) {
|
||||
if (card.isCreature() || card.isEnchanted()) {
|
||||
otherManaSources.add(card);
|
||||
continue; // don't use creatures before other permanents
|
||||
}
|
||||
|
||||
int usableManaAbilities = 0;
|
||||
boolean needsLimitedResources = false;
|
||||
boolean producesAnyColor = false;
|
||||
final ArrayList<SpellAbility> manaAbilities = getAIPlayableMana(card);
|
||||
|
||||
for (final SpellAbility m : manaAbilities) {
|
||||
if (m.getManaPart().isAnyMana()) {
|
||||
producesAnyColor = true;
|
||||
}
|
||||
|
||||
final Cost cost = m.getPayCosts();
|
||||
if (cost != null) {
|
||||
needsLimitedResources |= !cost.isReusuableResource();
|
||||
}
|
||||
|
||||
// if the AI can't pay the additional costs skip the mana ability
|
||||
if (cost != null) {
|
||||
m.setActivatingPlayer(ai);
|
||||
if (!CostPayment.canPayAdditionalCosts(m.getPayCosts(), m)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// don't use abilities with dangerous drawbacks
|
||||
AbilitySub sub = m.getSubAbility();
|
||||
if (sub != null && !card.getName().equals("Pristine Talisman") && !card.getName().equals("Zhur-Taa Druid")) {
|
||||
if (!SpellApiToAi.Converter.get(sub.getApi()).chkDrawbackWithSubs(ai, sub)) {
|
||||
continue;
|
||||
}
|
||||
needsLimitedResources = true; // TODO: check for good drawbacks (gainLife)
|
||||
}
|
||||
usableManaAbilities++;
|
||||
}
|
||||
|
||||
if (needsLimitedResources) {
|
||||
otherManaSources.add(card);
|
||||
} else if (producesAnyColor) {
|
||||
anyColorManaSources.add(card);
|
||||
} else if (usableManaAbilities == 1) {
|
||||
if (manaAbilities.get(0).getManaPart().mana().equals("1")) {
|
||||
colorlessManaSources.add(card);
|
||||
} else {
|
||||
oneManaSources.add(card);
|
||||
}
|
||||
} else if (usableManaAbilities == 2) {
|
||||
twoManaSources.add(card);
|
||||
} else if (usableManaAbilities == 3) {
|
||||
threeManaSources.add(card);
|
||||
} else if (usableManaAbilities == 4) {
|
||||
fourManaSources.add(card);
|
||||
} else {
|
||||
fiveManaSources.add(card);
|
||||
}
|
||||
}
|
||||
sortedManaSources.addAll(colorlessManaSources);
|
||||
sortedManaSources.addAll(oneManaSources);
|
||||
sortedManaSources.addAll(twoManaSources);
|
||||
sortedManaSources.addAll(threeManaSources);
|
||||
sortedManaSources.addAll(fourManaSources);
|
||||
sortedManaSources.addAll(fiveManaSources);
|
||||
sortedManaSources.addAll(anyColorManaSources);
|
||||
//use better creatures later
|
||||
ComputerUtilCard.sortByEvaluateCreature(otherManaSources);
|
||||
Collections.reverse(otherManaSources);
|
||||
sortedManaSources.addAll(otherManaSources);
|
||||
return sortedManaSources;
|
||||
} // getAvailableMana()
|
||||
|
||||
//This method is currently used by AI to estimate mana available
|
||||
private static Multimap<Integer, SpellAbility> groupSourcesByManaColor(final Player ai, boolean checkPlayable) {
|
||||
final Multimap<Integer, SpellAbility> manaMap = ArrayListMultimap.create();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
// Loop over all current available mana sources
|
||||
for (final Card sourceCard : getAvailableMana(ai, checkPlayable)) {
|
||||
for (final SpellAbility m : getAIPlayableMana(sourceCard)) {
|
||||
m.setActivatingPlayer(ai);
|
||||
if (checkPlayable && !m.canPlay()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// don't use abilities with dangerous drawbacks
|
||||
AbilitySub sub = m.getSubAbility();
|
||||
if (sub != null) {
|
||||
if (!SpellApiToAi.Converter.get(sub.getApi()).chkDrawbackWithSubs(ai, sub)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
manaMap.put(ManaAtom.COLORLESS, m); // add to colorless source list
|
||||
AbilityManaPart mp = m.getManaPart();
|
||||
|
||||
// setup produce mana replacement effects
|
||||
final Map<String, Object> repParams = new HashMap<String, Object>();
|
||||
repParams.put("Event", "ProduceMana");
|
||||
repParams.put("Mana", mp.getOrigProduced());
|
||||
repParams.put("Affected", sourceCard);
|
||||
repParams.put("Player", ai);
|
||||
repParams.put("AbilityMana", m);
|
||||
|
||||
for (final Player p : game.getPlayers()) {
|
||||
for (final Card crd : p.getAllCards()) {
|
||||
for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) {
|
||||
if (replacementEffect.requirementsCheck(game)
|
||||
&& replacementEffect.canReplace(repParams)
|
||||
&& replacementEffect.getMapParams().containsKey("ManaReplacement")
|
||||
&& replacementEffect.zonesCheck(game.getZoneOf(crd))) {
|
||||
String repType = crd.getSVar(replacementEffect.getMapParams().get("ManaReplacement"));
|
||||
if (repType.contains("Chosen")) {
|
||||
repType = repType.replace("Chosen", MagicColor.toShortString(crd.getChosenColor().get(0)));
|
||||
}
|
||||
mp.setManaReplaceType(repType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> reflectedColors = CardUtil.getReflectableManaColors(m);
|
||||
// find possible colors
|
||||
if (mp.canProduce("W", m) || reflectedColors.contains(MagicColor.Constant.WHITE)) {
|
||||
manaMap.put(ManaAtom.WHITE, m);
|
||||
}
|
||||
if (mp.canProduce("U", m) || reflectedColors.contains(MagicColor.Constant.BLUE)) {
|
||||
manaMap.put(ManaAtom.BLUE, m);
|
||||
}
|
||||
if (mp.canProduce("B", m) || reflectedColors.contains(MagicColor.Constant.BLACK)) {
|
||||
manaMap.put(ManaAtom.BLACK, m);
|
||||
}
|
||||
if (mp.canProduce("R", m) || reflectedColors.contains(MagicColor.Constant.RED)) {
|
||||
manaMap.put(ManaAtom.RED, m);
|
||||
}
|
||||
if (mp.canProduce("G", m) || reflectedColors.contains(MagicColor.Constant.GREEN)) {
|
||||
manaMap.put(ManaAtom.GREEN, m);
|
||||
}
|
||||
if (mp.isSnow()) {
|
||||
manaMap.put(ManaAtom.IS_SNOW, m);
|
||||
}
|
||||
} // end of mana abilities loop
|
||||
} // end of mana sources loop
|
||||
|
||||
return manaMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* determineLeftoverMana.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param player
|
||||
* a {@link forge.game.player.Player} object.
|
||||
* @return a int.
|
||||
* @since 1.0.15
|
||||
*/
|
||||
public static int determineLeftoverMana(final SpellAbility sa, final Player player) {
|
||||
for (int i = 1; i < 100; i++)
|
||||
if (!canPayManaCost(sa, player, i))
|
||||
return i - 1;
|
||||
|
||||
return 99;
|
||||
}
|
||||
|
||||
// Returns basic mana abilities plus "reflected mana" abilities
|
||||
/**
|
||||
* <p>
|
||||
* getAIPlayableMana.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link java.util.ArrayList} object.
|
||||
*/
|
||||
public static final ArrayList<SpellAbility> getAIPlayableMana(Card c) {
|
||||
final ArrayList<SpellAbility> res = new ArrayList<SpellAbility>();
|
||||
for (final SpellAbility a : c.getManaAbility()) {
|
||||
// if a mana ability has a mana cost the AI will miscalculate
|
||||
// if there is a parent ability the AI can't use it
|
||||
final Cost cost = a.getPayCosts();
|
||||
if (!cost.hasNoManaCost() || (a.getApi() != ApiType.Mana && a.getApi() != ApiType.ManaReflected)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!res.contains(a)) {
|
||||
res.add(a);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void handleOfferingsAI(final SpellAbility sa, boolean test, boolean costIsPaid) {
|
||||
if (sa.isOffering() && sa.getSacrificedAsOffering() != null) {
|
||||
final Card offering = sa.getSacrificedAsOffering();
|
||||
offering.setUsedToPay(false);
|
||||
if (costIsPaid && !test) {
|
||||
sa.getHostCard().getController().getGame().getAction().sacrifice(offering, sa);
|
||||
}
|
||||
sa.resetSacrificedAsOffering();
|
||||
}
|
||||
}
|
||||
}
|
||||
63
forge-ai/src/main/java/forge/ai/LobbyPlayerAi.java
Normal file
63
forge-ai/src/main/java/forge/ai/LobbyPlayerAi.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package forge.ai;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.player.LobbyPlayer;
|
||||
import forge.game.player.Player;
|
||||
|
||||
public class LobbyPlayerAi extends LobbyPlayer {
|
||||
public LobbyPlayerAi(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
private String aiProfile = "";
|
||||
private boolean rotateProfileEachGame;
|
||||
private boolean allowCheatShuffle;
|
||||
|
||||
public boolean isAllowCheatShuffle() {
|
||||
return allowCheatShuffle;
|
||||
}
|
||||
|
||||
|
||||
public void setAllowCheatShuffle(boolean allowCheatShuffle) {
|
||||
this.allowCheatShuffle = allowCheatShuffle;
|
||||
}
|
||||
|
||||
public void setAiProfile(String profileName) {
|
||||
aiProfile = profileName;
|
||||
}
|
||||
|
||||
public String getAiProfile() {
|
||||
return aiProfile;
|
||||
}
|
||||
|
||||
public void setRotateProfileEachGame(boolean rotateProfileEachGame) {
|
||||
this.rotateProfileEachGame = rotateProfileEachGame;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PlayerType getType() {
|
||||
return PlayerType.COMPUTER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerControllerAi createControllerFor(Player ai) {
|
||||
PlayerControllerAi result = new PlayerControllerAi(ai.getGame(), ai, this);
|
||||
result.allowCheatShuffle(allowCheatShuffle);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player createIngamePlayer(Game game) {
|
||||
Player ai = new Player(getName(), game);
|
||||
ai.setFirstController(createControllerFor(ai));
|
||||
|
||||
if( rotateProfileEachGame ) {
|
||||
setAiProfile(AiProfileUtil.getRandomProfile());
|
||||
System.out.println(String.format("AI profile %s was chosen for the lobby player %s.", getAiProfile(), getName()));
|
||||
}
|
||||
return ai;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hear(LobbyPlayer player, String message) { /* Local AI is deaf. */ }
|
||||
}
|
||||
765
forge-ai/src/main/java/forge/ai/PlayerControllerAi.java
Normal file
765
forge-ai/src/main/java/forge/ai/PlayerControllerAi.java
Normal file
@@ -0,0 +1,765 @@
|
||||
package forge.ai;
|
||||
|
||||
import com.esotericsoftware.minlog.Log;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import forge.ai.ability.ChangeZoneAi;
|
||||
import forge.ai.ability.CharmAi;
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostShard;
|
||||
import forge.deck.Deck;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.GameType;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostPartMana;
|
||||
import forge.game.mana.Mana;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.player.LobbyPlayer;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.player.PlayerController;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.spellability.*;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.WrappedAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.item.PaperCard;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
/**
|
||||
* A prototype for player controller class
|
||||
*
|
||||
* Handles phase skips for now.
|
||||
*/
|
||||
public class PlayerControllerAi extends PlayerController {
|
||||
private final AiController brains;
|
||||
|
||||
public PlayerControllerAi(Game game, Player p, LobbyPlayer lp) {
|
||||
super(game, p, lp);
|
||||
|
||||
brains = new AiController(p, game);
|
||||
}
|
||||
|
||||
public void allowCheatShuffle(boolean value){
|
||||
brains.allowCheatShuffle(value);
|
||||
}
|
||||
|
||||
|
||||
public SpellAbility getAbilityToPlay(List<SpellAbility> abilities, MouseEvent triggerEvent) {
|
||||
if (abilities.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return abilities.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this method.
|
||||
* @param c
|
||||
*/
|
||||
/**public void playFromSuspend(Card c) {
|
||||
final List<SpellAbility> choices = c.getBasicSpells();
|
||||
c.setSuspendCast(true);
|
||||
getAi().chooseAndPlaySa(choices, true, true);
|
||||
}**/
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this method.
|
||||
* @return
|
||||
*/
|
||||
public AiController getAi() {
|
||||
return brains;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PaperCard> sideboard(Deck deck, GameType gameType) {
|
||||
// AI does not know how to sideboard
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Card, Integer> assignCombatDamage(Card attacker, List<Card> blockers, int damageDealt, GameEntity defender, boolean overrideOrder) {
|
||||
return ComputerUtilCombat.distributeAIDamage(attacker, blockers, damageDealt, defender, overrideOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer announceRequirements(SpellAbility ability, String announce, boolean allowZero) {
|
||||
// For now, these "announcements" are made within the AI classes of the appropriate SA effects
|
||||
return null; // return incorrect value to indicate that
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Card> choosePermanentsToSacrifice(SpellAbility sa, int min, int max, List<Card> validTargets, String message) {
|
||||
return ComputerUtil.choosePermanentsToSacrifice(player, validTargets, max, sa, false, min == 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Card> choosePermanentsToDestroy(SpellAbility sa, int min, int max, List<Card> validTargets, String message) {
|
||||
return ComputerUtil.choosePermanentsToSacrifice(player, validTargets, max, sa, true, min == 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Card> chooseCardsForEffect(List<Card> sourceList, SpellAbility sa, String title, int min, int max, boolean isOptional) {
|
||||
return brains.chooseCardsForEffect(sourceList, sa, min, max, isOptional);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends GameEntity> T chooseSingleEntityForEffect(Collection<T> options, SpellAbility sa, String title, boolean isOptional, Player targetedPlayer) {
|
||||
ApiType api = sa.getApi();
|
||||
if (null == api) {
|
||||
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
|
||||
}
|
||||
return SpellApiToAi.Converter.get(api).chooseSingleEntity(player, sa, options, isOptional, targetedPlayer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSingleSpellForEffect(java.util.List<SpellAbility> spells, SpellAbility sa, String title) {
|
||||
ApiType api = sa.getApi();
|
||||
if (null == api) {
|
||||
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
|
||||
}
|
||||
return SpellApiToAi.Converter.get(api).chooseSingleSpellAbility(player, sa, spells);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return getAi().confirmAction(sa, mode, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) {
|
||||
return getAi().confirmStaticApplication(hostCard, affected, logic, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmTrigger(SpellAbility sa, Trigger regtrig, Map<String, String> triggerParams, boolean isMandatory) {
|
||||
if (triggerParams.containsKey("DelayedTrigger")) {
|
||||
//TODO: The only card with an optional delayed trigger is Shirei, Shizo's Caretaker,
|
||||
// needs to be expanded when a more difficult cards comes up
|
||||
return true;
|
||||
}
|
||||
// Store/replace target choices more properly to get this SA cleared.
|
||||
TargetChoices tc = null;
|
||||
TargetChoices subtc = null;
|
||||
boolean storeChoices = sa.getTargetRestrictions() != null;
|
||||
final SpellAbility sub = sa.getSubAbility();
|
||||
boolean storeSubChoices = sub != null && sub.getTargetRestrictions() != null;
|
||||
boolean ret = true;
|
||||
|
||||
if (storeChoices) {
|
||||
tc = sa.getTargets();
|
||||
sa.resetTargets();
|
||||
}
|
||||
if (storeSubChoices) {
|
||||
subtc = sub.getTargets();
|
||||
sub.resetTargets();
|
||||
}
|
||||
// There is no way this doTrigger here will have the same target as stored above
|
||||
// So it's possible it's making a different decision here than will actually happen
|
||||
if (!brains.doTrigger(sa, isMandatory)) {
|
||||
ret = false;
|
||||
}
|
||||
if (storeChoices) {
|
||||
sa.resetTargets();
|
||||
sa.setTargets(tc);
|
||||
}
|
||||
if (storeSubChoices) {
|
||||
sub.resetTargets();
|
||||
sub.setTargets(subtc);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getWillPlayOnFirstTurn(boolean isFirstGame) {
|
||||
return true; // AI is brave :)
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Card> orderBlockers(Card attacker, List<Card> blockers) {
|
||||
return AiBlockController.orderBlockers(attacker, blockers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Card> orderAttackers(Card blocker, List<Card> attackers) {
|
||||
return AiBlockController.orderAttackers(blocker, attackers);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.player.PlayerController#reveal(java.lang.String, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public void reveal(Collection<Card> cards, ZoneType zone, Player owner, String messagePrefix) {
|
||||
// We don't know how to reveal cards to AI
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutablePair<List<Card>, List<Card>> arrangeForScry(List<Card> topN) {
|
||||
List<Card> toBottom = new ArrayList<Card>();
|
||||
List<Card> toTop = new ArrayList<Card>();
|
||||
|
||||
for (Card c: topN) {
|
||||
if (ComputerUtil.scryWillMoveCardToBottomOfLibrary(player, c)) {
|
||||
toBottom.add(c);
|
||||
}
|
||||
else {
|
||||
toTop.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
// put the rest on top in random order
|
||||
Collections.shuffle(toTop);
|
||||
return ImmutablePair.of(toTop, toBottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean willPutCardOnTop(Card c) {
|
||||
return true; // AI does not know what will happen next (another clash or that would become his topdeck)
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Card> orderMoveToZoneList(List<Card> cards, ZoneType destinationZone) {
|
||||
//TODO Add logic for AI ordering here
|
||||
return cards;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Card> chooseCardsToDiscardFrom(Player p, SpellAbility sa, List<Card> validCards, int min, int max) {
|
||||
if (p == player) {
|
||||
return brains.getCardsToDiscard(min, max, validCards, sa);
|
||||
}
|
||||
|
||||
boolean isTargetFriendly = !p.isOpponentOf(player);
|
||||
|
||||
return isTargetFriendly
|
||||
? ComputerUtil.getCardsToDiscardFromFriend(player, p, sa, validCards, min, max)
|
||||
: ComputerUtil.getCardsToDiscardFromOpponent(player, p, sa, validCards, min, max);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.player.PlayerController#mayPlaySpellAbilityForFree(forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
public void playSpellAbilityForFree(SpellAbility copySA, boolean mayChooseNewTargets) {
|
||||
// Ai is known to set targets in doTrigger, so if it cannot choose new targets, we won't call canPlays
|
||||
if (mayChooseNewTargets) {
|
||||
if (copySA instanceof Spell) {
|
||||
Spell spell = (Spell) copySA;
|
||||
((PlayerControllerAi) player.getController()).getAi().canPlayFromEffectAI(spell, true, true);
|
||||
}
|
||||
else {
|
||||
getAi().canPlaySa(copySA);
|
||||
}
|
||||
}
|
||||
ComputerUtil.playSpellAbilityForFree(player, copySA);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playSpellAbilityNoStack(SpellAbility effectSA, boolean canSetupTargets) {
|
||||
if (canSetupTargets)
|
||||
brains.doTrigger(effectSA, true); // first parameter does not matter, since return value won't be used
|
||||
ComputerUtil.playNoStack(player, effectSA, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playMiracle(SpellAbility miracle, Card card) {
|
||||
getAi().chooseAndPlaySa(false, false, miracle);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.player.PlayerController#chooseCardsToDelve(int, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public List<Card> chooseCardsToDelve(int colorlessCost, List<Card> grave) {
|
||||
return getAi().chooseCardsToDelve(colorlessCost, grave);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.player.PlayerController#chooseTargets(forge.card.spellability.SpellAbility, forge.card.spellability.SpellAbilityStackInstance)
|
||||
*/
|
||||
@Override
|
||||
public TargetChoices chooseNewTargetsFor(SpellAbility ability) {
|
||||
// AI currently can't do this. But when it can it will need to be based on Ability API
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.player.PlayerController#chooseCardsToDiscardUnlessType(int, java.util.List, java.lang.String, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
public List<Card> chooseCardsToDiscardUnlessType(int num, List<Card> hand, String uType, SpellAbility sa) {
|
||||
final List<Card> cardsOfType = CardLists.getType(hand, uType);
|
||||
if (!cardsOfType.isEmpty()) {
|
||||
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
|
||||
return Lists.newArrayList(toDiscard);
|
||||
}
|
||||
return getAi().getCardsToDiscard(num, (String[])null, sa);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mana chooseManaFromPool(List<Mana> manaChoices) {
|
||||
return manaChoices.get(0); // no brains used
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.player.PlayerController#ChooseSomeType(java.lang.String, java.util.List, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public String chooseSomeType(String kindOfType, SpellAbility sa, List<String> validTypes, List<String> invalidTypes, boolean isOptional) {
|
||||
String chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), invalidTypes);
|
||||
if (StringUtils.isBlank(chosen) && !validTypes.isEmpty())
|
||||
{
|
||||
chosen = validTypes.get(0);
|
||||
Log.warn("AI has no idea how to choose " + kindOfType +", defaulting to 1st element: chosen");
|
||||
}
|
||||
game.getAction().nofityOfValue(sa, null, "Computer picked: " + chosen, player);
|
||||
return chosen;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.player.PlayerController#confirmReplacementEffect(forge.card.replacement.ReplacementEffect, forge.card.spellability.SpellAbility, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) {
|
||||
return brains.aiShouldRun(replacementEffect, effectSA);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Card> getCardsToMulligan(boolean isCommander, Player firstPlayer) {
|
||||
if (!ComputerUtil.wantMulligan(player)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isCommander) {
|
||||
return player.getCardsIn(ZoneType.Hand);
|
||||
}
|
||||
else {
|
||||
return ComputerUtil.getPartialParisCandidates(player);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declareAttackers(Player attacker, Combat combat) {
|
||||
brains.declareAttackers(attacker, combat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declareBlockers(Player defender, Combat combat) {
|
||||
brains.declareBlockersFor(defender, combat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSpellAbilityToPlay() {
|
||||
return brains.choooseSpellAbilityToPlay();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playChosenSpellAbility(SpellAbility sa)
|
||||
{
|
||||
// System.out.println("Playing sa: " + sa);
|
||||
if ( sa == Ability.PLAY_LAND_SURROGATE )
|
||||
player.playLand(sa.getHostCard(), false);
|
||||
else
|
||||
ComputerUtil.handlePlayingSpellAbility(player, sa, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Card> chooseCardsToDiscardToMaximumHandSize(int numDiscard) {
|
||||
return brains.getCardsToDiscard(numDiscard, (String[])null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Card> chooseCardsToRevealFromHand(int min, int max, List<Card> valid) {
|
||||
int numCardsToReveal = Math.min(max, valid.size());
|
||||
return numCardsToReveal == 0 ? Lists.<Card>newArrayList() : valid.subList(0, numCardsToReveal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean payManaOptional(Card c, Cost cost, SpellAbility sa, String prompt, ManaPaymentPurpose purpose) {
|
||||
final Ability ability = new AbilityStatic(c, cost, null) { @Override public void resolve() {} };
|
||||
ability.setActivatingPlayer(c.getController());
|
||||
|
||||
if (ComputerUtilCost.canPayCost(ability, c.getController())) {
|
||||
ComputerUtil.playNoStack(c.getController(), ability, game);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SpellAbility> chooseSaToActivateFromOpeningHand(List<SpellAbility> usableFromOpeningHand) {
|
||||
// AI would play everything. But limits to one copy of (Leyline of Singularity) and (Gemstone Caverns)
|
||||
return brains.chooseSaToActivateFromOpeningHand(usableFromOpeningHand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int chooseNumber(SpellAbility sa, String title, int min, int max) {
|
||||
return brains.chooseNumber(sa, title, min, max);
|
||||
}
|
||||
|
||||
public int chooseNumber(SpellAbility sa, String title, List<Integer> options, Player relatedPlayer) {
|
||||
return brains.chooseNumber(sa, title, options, relatedPlayer);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.player.PlayerController#chooseFlipResult(forge.Card, forge.game.player.Player, java.lang.String[], boolean)
|
||||
*/
|
||||
@Override
|
||||
public boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call) {
|
||||
if (call) {
|
||||
// Win if possible
|
||||
boolean result = false;
|
||||
for (boolean s : results) {
|
||||
if (s) {
|
||||
result = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
// heads or tails, AI doesn't know which is better now
|
||||
int i = MyRandom.getRandom().nextInt(results.length);
|
||||
return results[i];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<SpellAbilityStackInstance, GameObject> chooseTarget(SpellAbility saSrc, List<Pair<SpellAbilityStackInstance, GameObject>> allTargets) {
|
||||
// TODO Teach AI how to use Spellskite
|
||||
return allTargets.get(0);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void notifyOfValue(SpellAbility saSource, GameObject realtedTarget, String value) {
|
||||
// AI should take into consideration creature types, numbers and other information (mostly choices) arriving through this channel
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Boolean defaultVal) {
|
||||
switch(kindOfChoice) {
|
||||
case TapOrUntap: return true;
|
||||
case UntapOrLeaveTapped: return defaultVal != null && defaultVal.booleanValue();
|
||||
case UntapTimeVault: return false; // TODO Should AI skip his turn for time vault?
|
||||
default:
|
||||
return MyRandom.getRandom().nextBoolean();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card chooseProtectionShield(GameEntity entityBeingDamaged, List<String> options, Map<String, Card> choiceMap) {
|
||||
int i = MyRandom.getRandom().nextInt(options.size());
|
||||
return choiceMap.get(options.get(i));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.player.PlayerController#chooseModeForAbility(forge.card.spellability.SpellAbility, java.util.List, int, int)
|
||||
*/
|
||||
@Override
|
||||
public List<AbilitySub> chooseModeForAbility(SpellAbility sa, int min, int num) {
|
||||
return CharmAi.chooseOptionsAi(sa, player, sa.isTrigger(), num, min, !player.equals(sa.getActivatingPlayer()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<CounterType,String> chooseAndRemoveOrPutCounter(Card cardWithCounter) {
|
||||
if (!cardWithCounter.hasCounters()) {
|
||||
System.out.println("chooseCounterType was reached with a card with no counters on it. Consider filtering this card out earlier");
|
||||
return null;
|
||||
}
|
||||
|
||||
final Player controller = cardWithCounter.getController();
|
||||
final List<Player> enemies = player.getOpponents();
|
||||
final List<Player> allies = player.getAllies();
|
||||
allies.add(player);
|
||||
|
||||
List<CounterType> countersToIncrease = new ArrayList<CounterType>();
|
||||
List<CounterType> countersToDecrease = new ArrayList<CounterType>();
|
||||
|
||||
for (final CounterType counter : cardWithCounter.getCounters().keySet()) {
|
||||
if ((!ComputerUtil.isNegativeCounter(counter, cardWithCounter) && allies.contains(controller))
|
||||
|| (ComputerUtil.isNegativeCounter(counter, cardWithCounter) && enemies.contains(controller))) {
|
||||
countersToIncrease.add(counter);
|
||||
} else {
|
||||
countersToDecrease.add(counter);
|
||||
}
|
||||
}
|
||||
|
||||
if (!countersToIncrease.isEmpty()) {
|
||||
int random = MyRandom.getRandom().nextInt(countersToIncrease.size());
|
||||
return new ImmutablePair<CounterType,String>(countersToIncrease.get(random),"Put");
|
||||
}
|
||||
else if (!countersToDecrease.isEmpty()) {
|
||||
int random = MyRandom.getRandom().nextInt(countersToDecrease.size());
|
||||
return new ImmutablePair<CounterType,String>(countersToDecrease.get(random),"Remove");
|
||||
}
|
||||
|
||||
// shouldn't reach here but just in case, remove random counter
|
||||
List<CounterType> countersOnCard = new ArrayList<CounterType>();
|
||||
int random = MyRandom.getRandom().nextInt(countersOnCard.size());
|
||||
return new ImmutablePair<CounterType,String>(countersOnCard.get(random),"Remove");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) {
|
||||
final String c = ComputerUtilCard.getMostProminentColor(player.getCardsIn(ZoneType.Hand));
|
||||
byte chosenColorMask = MagicColor.fromName(c);
|
||||
if ((colors.getColor() & chosenColorMask) != 0) {
|
||||
return chosenColorMask;
|
||||
} else {
|
||||
return Iterables.getFirst(colors, (byte)0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte chooseColor(String message, SpellAbility sa, ColorSet colors) {
|
||||
// You may switch on sa.getApi() here and use sa.getParam("AILogic")
|
||||
List<Card> hand = new ArrayList<Card>(player.getCardsIn(ZoneType.Hand));
|
||||
if( sa.getApi() == ApiType.Mana )
|
||||
hand.addAll(player.getCardsIn(ZoneType.Stack));
|
||||
final String c = ComputerUtilCard.getMostProminentColor(hand);
|
||||
byte chosenColorMask = MagicColor.fromName(c);
|
||||
|
||||
|
||||
if ((colors.getColor() & chosenColorMask) != 0) {
|
||||
return chosenColorMask;
|
||||
} else {
|
||||
return Iterables.getFirst(colors, MagicColor.WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaperCard chooseSinglePaperCard(SpellAbility sa, String message,
|
||||
Predicate<PaperCard> cpp, String name) {
|
||||
throw new UnsupportedOperationException("Should not be called for AI"); // or implement it if you know how
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> chooseColors(String message, SpellAbility sa, int min, int max, List<String> options) {
|
||||
return ComputerUtilCard.chooseColor(sa, min, max, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CounterType chooseCounterType(Collection<CounterType> options, SpellAbility sa, String prompt) {
|
||||
// may write a smarter AI if you need to (with calls to AI-clas for given API ability)
|
||||
|
||||
// TODO: ArsenalNut (06 Feb 12)computer needs
|
||||
// better logic to pick a counter type and probably
|
||||
// an initial target
|
||||
// find first nonzero counter on target
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmPayment(CostPart costPart, String prompt) {
|
||||
return brains.confirmPayment(costPart); // AI is expected to know what it is paying for at the moment (otherwise add another parameter to this method)
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplacementEffect chooseSingleReplacementEffect(String prompt, List<ReplacementEffect> possibleReplacers, HashMap<String, Object> runParams) {
|
||||
// AI logic for choosing which replacement effect to apply
|
||||
// happens here.
|
||||
return possibleReplacers.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseProtectionType(String string, SpellAbility sa, List<String> choices) {
|
||||
String choice = choices.get(0);
|
||||
final String logic = sa.getParam("AILogic");
|
||||
if (logic == null || logic.equals("MostProminentHumanCreatures")) {
|
||||
List<Card> list = new ArrayList<Card>();
|
||||
for (Player opp : player.getOpponents()) {
|
||||
list.addAll(opp.getCreaturesInPlay());
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
list = CardLists.filterControlledBy(game.getCardsInGame(), player.getOpponents());
|
||||
}
|
||||
if (!list.isEmpty()) {
|
||||
choice = ComputerUtilCard.getMostProminentColor(list);
|
||||
}
|
||||
}
|
||||
return choice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean payCostToPreventEffect(Cost cost, SpellAbility sa, boolean alreadyPaid, List<Player> allPayers) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
|
||||
emptyAbility.setActivatingPlayer(player);
|
||||
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
|
||||
ComputerUtil.playNoStack(player, emptyAbility, game); // AI needs something to resolve to pay that cost
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void orderAndPlaySimultaneousSa(List<SpellAbility> activePlayerSAs) {
|
||||
for (final SpellAbility sa : activePlayerSAs) {
|
||||
prepareSingleSa(sa.getHostCard(),sa,true);
|
||||
ComputerUtil.playStack(sa, player, game);
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareSingleSa(final Card host, final SpellAbility sa, boolean isMandatory){
|
||||
if (sa.hasParam("TargetingPlayer")) {
|
||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||
targetingPlayer.getController().chooseTargetsFor(sa);
|
||||
} else {
|
||||
brains.doTrigger(sa, isMandatory);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playTrigger(Card host, WrappedAbility wrapperAbility, boolean isMandatory) {
|
||||
prepareSingleSa(host, wrapperAbility, isMandatory);
|
||||
ComputerUtil.playNoStack(wrapperAbility.getActivatingPlayer(), wrapperAbility, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean playSaFromPlayEffect(SpellAbility tgtSA) {
|
||||
boolean optional = tgtSA.hasParam("Optional");
|
||||
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
|
||||
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
||||
Spell spell = (Spell) tgtSA;
|
||||
if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) {
|
||||
if (noManaCost) {
|
||||
ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, game);
|
||||
} else {
|
||||
ComputerUtil.playStack(tgtSA, player, game);
|
||||
}
|
||||
} else
|
||||
return false; // didn't play spell
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<GameEntity, CounterType> chooseProliferation() {
|
||||
return brains.chooseProliferation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chooseTargetsFor(SpellAbility currentAbility) {
|
||||
return brains.doTrigger(currentAbility, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chooseCardsPile(SpellAbility sa, List<Card> pile1, List<Card> pile2, boolean faceUp) {
|
||||
if (!faceUp) {
|
||||
// AI will choose the first pile if it is larger or the same
|
||||
// TODO Improve this to be slightly more random to not be so predictable
|
||||
return pile1.size() >= pile2.size();
|
||||
} else {
|
||||
boolean allCreatures = Iterables.all(Iterables.concat(pile1, pile2), CardPredicates.Presets.CREATURES);
|
||||
int cmc1 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile1) : ComputerUtilCard.evaluatePermanentList(pile1);
|
||||
int cmc2 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile2) : ComputerUtilCard.evaluatePermanentList(pile2);
|
||||
System.out.println("value:" + cmc1 + " " + cmc2);
|
||||
|
||||
// for now, this assumes that the outcome will be bad
|
||||
// TODO: This should really have a ChooseLogic param to
|
||||
// figure this out
|
||||
return "Worst".equals(sa.getParam("AILogic")) ^ (cmc1 >= cmc2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revealAnte(String message, Multimap<Player, PaperCard> removedAnteCards) {
|
||||
// Ai won't understand that anyway
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends PaperCard> complainCardsCantPlayWell(Deck myDeck) {
|
||||
return brains.complainCardsCantPlayWell(myDeck);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Card> cheatShuffle(List<Card> list) {
|
||||
return brains.getBooleanProperty(AiProps.CHEAT_WITH_MANA_ON_SHUFFLE) ? brains.cheatShuffle(list) : list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardShields chooseRegenerationShield(Card c) {
|
||||
return Iterables.getFirst(c.getShield(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PaperCard> chooseCardsYouWonToAddToDeck(List<PaperCard> losses) {
|
||||
// TODO AI takes all by default
|
||||
return losses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt /* ai needs hints as well */ ) {
|
||||
// TODO Auto-generated method stub
|
||||
ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, false, 0);
|
||||
return ComputerUtilMana.payManaCost(cost, sa, player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Card, ManaCostShard> chooseCardsForConvoke(SpellAbility sa, ManaCost manaCost,
|
||||
List<Card> untappedCreats) {
|
||||
// TODO: AI to choose a creature to tap would go here
|
||||
// Probably along with deciding how many creatures to tap
|
||||
return new HashMap<Card, ManaCostShard>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseCardName(SpellAbility sa, Predicate<PaperCard> cpp, String valid, String message) {
|
||||
if (sa.hasParam("AILogic")) {
|
||||
final String logic = sa.getParam("AILogic");
|
||||
if (logic.equals("MostProminentInComputerDeck")) {
|
||||
return ComputerUtilCard.getMostProminentCardName(player.getCardsIn(ZoneType.Library));
|
||||
} else if (logic.equals("MostProminentInHumanDeck")) {
|
||||
return ComputerUtilCard.getMostProminentCardName(player.getOpponent().getCardsIn(ZoneType.Library));
|
||||
} else if (logic.equals("BestCreatureInComputerDeck")) {
|
||||
return ComputerUtilCard.getBestCreatureAI(player.getCardsIn(ZoneType.Library)).getName();
|
||||
} else if (logic.equals("RandomInComputerDeck")) {
|
||||
return Aggregates.random(player.getCardsIn(ZoneType.Library)).getName();
|
||||
}
|
||||
} else {
|
||||
List<Card> list = CardLists.filterControlledBy(game.getCardsInGame(), player.getOpponent());
|
||||
list = CardLists.filter(list, Predicates.not(Presets.LANDS));
|
||||
if (!list.isEmpty()) {
|
||||
return list.get(0).getName();
|
||||
}
|
||||
}
|
||||
return "Morphling";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCardForZoneChange(ZoneType destination,
|
||||
List<ZoneType> origin, SpellAbility sa, List<Card> fetchList,
|
||||
String selectPrompt, boolean b, Player decider) {
|
||||
|
||||
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player, decider);
|
||||
}
|
||||
|
||||
}
|
||||
175
forge-ai/src/main/java/forge/ai/SpellAbilityAi.java
Normal file
175
forge-ai/src/main/java/forge/ai/SpellAbilityAi.java
Normal file
@@ -0,0 +1,175 @@
|
||||
package forge.ai;
|
||||
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.SaTargetRoutines;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class SpellAbilityAi extends SaTargetRoutines {
|
||||
|
||||
public final boolean canPlayAIWithSubs(final Player aiPlayer, final SpellAbility sa) {
|
||||
if (!canPlayAI(aiPlayer, sa)) {
|
||||
return false;
|
||||
}
|
||||
final AbilitySub subAb = sa.getSubAbility();
|
||||
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb);
|
||||
}
|
||||
|
||||
protected abstract boolean canPlayAI(final Player aiPlayer, final SpellAbility sa);
|
||||
|
||||
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
if (!ComputerUtilCost.canPayCost(sa, aiPlayer) && !mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory);
|
||||
}
|
||||
|
||||
public final boolean doTriggerNoCostWithSubs(final Player aiPlayer, final SpellAbility sa, final boolean mandatory)
|
||||
{
|
||||
if (!doTriggerAINoCost(aiPlayer, sa, mandatory)) {
|
||||
return false;
|
||||
}
|
||||
final AbilitySub subAb = sa.getSubAbility();
|
||||
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb) || mandatory;
|
||||
}
|
||||
|
||||
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
if (canPlayAI(aiPlayer, sa)) {
|
||||
return true;
|
||||
}
|
||||
if (mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Player opp = aiPlayer.getOpponent();
|
||||
if (tgt != null) {
|
||||
if (opp.canBeTargetedBy(sa)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
} else if (mandatory) {
|
||||
if (aiPlayer.canBeTargetedBy(sa)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* isSorcerySpeed.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
protected static boolean isSorcerySpeed(final SpellAbility sa) {
|
||||
return ( sa.isSpell() && sa.getHostCard().isSorcery() )
|
||||
|| ( sa.isAbility() && sa.getRestrictions().isSorcerySpeed() );
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* playReusable.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
protected static boolean playReusable(final Player ai, final SpellAbility sa) {
|
||||
// TODO probably also consider if winter orb or similar are out
|
||||
|
||||
if (sa.getPayCosts() == null || sa instanceof AbilitySub) {
|
||||
return true; // This is only true for Drawbacks and triggers
|
||||
}
|
||||
|
||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!sa.getPayCosts().isReusuableResource()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.getRestrictions().getPlaneswalker() && ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {
|
||||
return true;
|
||||
}
|
||||
if (sa.isTrigger()) {
|
||||
return true;
|
||||
}
|
||||
if (sa.isSpell() && !sa.isBuyBackAbility()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PhaseHandler phase = ai.getGame().getPhaseHandler();
|
||||
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this method.
|
||||
* @param ai
|
||||
* @param subAb
|
||||
* @return
|
||||
*/
|
||||
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
|
||||
final AbilitySub subAb = ab.getSubAbility();
|
||||
return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
|
||||
}
|
||||
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
|
||||
T firstOption = Iterables.getFirst(options, null);
|
||||
|
||||
if( firstOption instanceof Card)
|
||||
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer);
|
||||
else if ( firstOption instanceof Player)
|
||||
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleSpellAbility is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return spells.get(0);
|
||||
}
|
||||
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleEntity is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return Iterables.getFirst(options, null);
|
||||
|
||||
}
|
||||
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> options) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleEntity is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
}
|
||||
153
forge-ai/src/main/java/forge/ai/SpellApiToAi.java
Normal file
153
forge-ai/src/main/java/forge/ai/SpellApiToAi.java
Normal file
@@ -0,0 +1,153 @@
|
||||
package forge.ai;
|
||||
|
||||
import java.util.EnumMap;
|
||||
|
||||
import forge.ai.ability.*;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.util.ReflectionUtil;
|
||||
|
||||
public enum SpellApiToAi {
|
||||
Converter;
|
||||
|
||||
private final static EnumMap<ApiType, Class<? extends SpellAbilityAi>> apiToClass = new EnumMap<>(ApiType.class);
|
||||
private final EnumMap<ApiType, SpellAbilityAi> apiToInstance = new EnumMap<>(ApiType.class);
|
||||
|
||||
static {
|
||||
apiToClass.put(ApiType.Abandon, AlwaysPlayAi.class);
|
||||
apiToClass.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class);
|
||||
apiToClass.put(ApiType.AddPhase, AddPhaseAi.class);
|
||||
apiToClass.put(ApiType.AddTurn, AddTurnAi.class);
|
||||
apiToClass.put(ApiType.Animate, AnimateAi.class);
|
||||
apiToClass.put(ApiType.AnimateAll, AnimateAllAi.class);
|
||||
apiToClass.put(ApiType.Attach, AttachAi.class);
|
||||
apiToClass.put(ApiType.Balance, BalanceAi.class);
|
||||
apiToClass.put(ApiType.BecomesBlocked, BecomesBlockedAi.class);
|
||||
apiToClass.put(ApiType.Bond, BondAi.class);
|
||||
apiToClass.put(ApiType.ChangeTargets, ChangeTargetsAi.class);
|
||||
apiToClass.put(ApiType.ChangeZone, ChangeZoneAi.class);
|
||||
apiToClass.put(ApiType.ChangeZoneAll, ChangeZoneAllAi.class);
|
||||
|
||||
apiToClass.put(ApiType.Charm, CharmAi.class);
|
||||
apiToClass.put(ApiType.ChooseCard, ChooseCardAi.class);
|
||||
apiToClass.put(ApiType.ChooseColor, ChooseColorAi.class);
|
||||
apiToClass.put(ApiType.ChooseNumber, CannotPlayAi.class);
|
||||
apiToClass.put(ApiType.ChoosePlayer, ChoosePlayerAi.class);
|
||||
apiToClass.put(ApiType.ChooseSource, ChooseSourceAi.class);
|
||||
apiToClass.put(ApiType.ChooseType, ChooseTypeAi.class);
|
||||
apiToClass.put(ApiType.Clash, ClashAi.class);
|
||||
apiToClass.put(ApiType.Cleanup, AlwaysPlayAi.class);
|
||||
apiToClass.put(ApiType.Clone, CloneAi.class);
|
||||
apiToClass.put(ApiType.CopyPermanent, CopyPermanentAi.class);
|
||||
apiToClass.put(ApiType.CopySpellAbility, CanPlayAsDrawbackAi.class);
|
||||
apiToClass.put(ApiType.ControlPlayer, CannotPlayAi.class);
|
||||
apiToClass.put(ApiType.ControlSpell, CannotPlayAi.class);
|
||||
apiToClass.put(ApiType.Counter, CounterAi.class);
|
||||
apiToClass.put(ApiType.DamageAll, DamageAllAi.class);
|
||||
|
||||
apiToClass.put(ApiType.DealDamage, DamageDealAi.class);
|
||||
apiToClass.put(ApiType.Debuff, DebuffAi.class);
|
||||
apiToClass.put(ApiType.DebuffAll, DebuffAllAi.class);
|
||||
apiToClass.put(ApiType.DeclareCombatants, CannotPlayAi.class);
|
||||
apiToClass.put(ApiType.DelayedTrigger, DelayedTriggerAi.class);
|
||||
apiToClass.put(ApiType.Destroy, DestroyAi.class);
|
||||
apiToClass.put(ApiType.DestroyAll, DestroyAllAi.class);
|
||||
apiToClass.put(ApiType.Dig, DigAi.class);
|
||||
apiToClass.put(ApiType.DigUntil, DigUntilAi.class);
|
||||
apiToClass.put(ApiType.Discard, DiscardAi.class);
|
||||
apiToClass.put(ApiType.DrainMana, DrainManaAi.class);
|
||||
apiToClass.put(ApiType.Draw, DrawAi.class);
|
||||
apiToClass.put(ApiType.EachDamage, DamageEachAi.class);
|
||||
apiToClass.put(ApiType.Effect, EffectAi.class);
|
||||
apiToClass.put(ApiType.Encode, EncodeAi.class);
|
||||
apiToClass.put(ApiType.EndTurn, EndTurnAi.class);
|
||||
apiToClass.put(ApiType.ExchangeLife, LifeExchangeAi.class);
|
||||
apiToClass.put(ApiType.ExchangeControl, ControlExchangeAi.class);
|
||||
apiToClass.put(ApiType.ExchangePower, PowerExchangeAi.class);
|
||||
apiToClass.put(ApiType.ExchangeZone, ZoneExchangeAi.class);
|
||||
apiToClass.put(ApiType.Fight, FightAi.class);
|
||||
apiToClass.put(ApiType.FlipACoin, FlipACoinAi.class);
|
||||
apiToClass.put(ApiType.Fog, FogAi.class);
|
||||
apiToClass.put(ApiType.GainControl, ControlGainAi.class);
|
||||
apiToClass.put(ApiType.GainLife, LifeGainAi.class);
|
||||
apiToClass.put(ApiType.GainOwnership, CannotPlayAi.class);
|
||||
apiToClass.put(ApiType.GenericChoice, ChooseGenericEffectAi.class);
|
||||
apiToClass.put(ApiType.LoseLife, LifeLoseAi.class);
|
||||
apiToClass.put(ApiType.LosesGame, GameLossAi.class);
|
||||
apiToClass.put(ApiType.Mana, ManaEffectAi.class);
|
||||
apiToClass.put(ApiType.ManaReflected, CannotPlayAi.class);
|
||||
apiToClass.put(ApiType.Mill, MillAi.class);
|
||||
apiToClass.put(ApiType.MoveCounter, CountersMoveAi.class);
|
||||
apiToClass.put(ApiType.MultiplePiles, CannotPlayAi.class);
|
||||
apiToClass.put(ApiType.MustAttack, MustAttackAi.class);
|
||||
apiToClass.put(ApiType.MustBlock, MustBlockAi.class);
|
||||
apiToClass.put(ApiType.NameCard, ChooseCardNameAi.class);
|
||||
apiToClass.put(ApiType.PeekAndReveal, PeekAndRevealAi.class);
|
||||
apiToClass.put(ApiType.PermanentCreature, PermanentCreatureAi.class);
|
||||
apiToClass.put(ApiType.PermanentNoncreature, PermanentNoncreatureAi.class);
|
||||
apiToClass.put(ApiType.Phases, PhasesAi.class);
|
||||
apiToClass.put(ApiType.Planeswalk, AlwaysPlayAi.class);
|
||||
apiToClass.put(ApiType.Play, PlayAi.class);
|
||||
apiToClass.put(ApiType.Poison, PoisonAi.class);
|
||||
apiToClass.put(ApiType.PreventDamage, DamagePreventAi.class);
|
||||
apiToClass.put(ApiType.PreventDamageAll, DamagePreventAllAi.class);
|
||||
apiToClass.put(ApiType.Proliferate, CountersProliferateAi.class);
|
||||
apiToClass.put(ApiType.Protection, ProtectAi.class);
|
||||
apiToClass.put(ApiType.ProtectionAll, ProtectAllAi.class);
|
||||
apiToClass.put(ApiType.Pump, PumpAi.class);
|
||||
apiToClass.put(ApiType.PumpAll, PumpAllAi.class);
|
||||
apiToClass.put(ApiType.PutCounter, CountersPutAi.class);
|
||||
apiToClass.put(ApiType.PutCounterAll, CountersPutAllAi.class);
|
||||
apiToClass.put(ApiType.RearrangeTopOfLibrary, RearrangeTopOfLibraryAi.class);
|
||||
apiToClass.put(ApiType.Regenerate, RegenerateAi.class);
|
||||
apiToClass.put(ApiType.RegenerateAll, RegenerateAllAi.class);
|
||||
apiToClass.put(ApiType.RemoveCounter, CountersRemoveAi.class);
|
||||
apiToClass.put(ApiType.RemoveCounterAll, CannotPlayAi.class);
|
||||
apiToClass.put(ApiType.RemoveFromCombat, RemoveFromCombatAi.class);
|
||||
apiToClass.put(ApiType.ReorderZone, AlwaysPlayAi.class);
|
||||
apiToClass.put(ApiType.Repeat, RepeatAi.class);
|
||||
apiToClass.put(ApiType.RepeatEach, RepeatEachAi.class);
|
||||
apiToClass.put(ApiType.RestartGame, RestartGameAi.class);
|
||||
apiToClass.put(ApiType.Reveal, RevealAi.class);
|
||||
apiToClass.put(ApiType.RevealHand, RevealHandAi.class);
|
||||
apiToClass.put(ApiType.RollPlanarDice, RollPlanarDiceAi.class);
|
||||
apiToClass.put(ApiType.RunSVarAbility, AlwaysPlayAi.class);
|
||||
apiToClass.put(ApiType.Sacrifice, SacrificeAi.class);
|
||||
apiToClass.put(ApiType.SacrificeAll, SacrificeAllAi.class);
|
||||
apiToClass.put(ApiType.Scry, ScryAi.class);
|
||||
apiToClass.put(ApiType.SetInMotion, AlwaysPlayAi.class);
|
||||
apiToClass.put(ApiType.SetLife, LifeSetAi.class);
|
||||
apiToClass.put(ApiType.SetState, SetStateAi.class);
|
||||
apiToClass.put(ApiType.Shuffle, ShuffleAi.class);
|
||||
apiToClass.put(ApiType.SkipTurn, CannotPlayAi.class);
|
||||
apiToClass.put(ApiType.StoreSVar, StoreSVarAi.class);
|
||||
apiToClass.put(ApiType.Tap, TapAi.class);
|
||||
apiToClass.put(ApiType.TapAll, TapAllAi.class);
|
||||
apiToClass.put(ApiType.TapOrUntap, TapOrUntapAi.class);
|
||||
apiToClass.put(ApiType.TapOrUntapAll, TapOrUntapAllAi.class);
|
||||
apiToClass.put(ApiType.Token, TokenAi.class);
|
||||
apiToClass.put(ApiType.TwoPiles, TwoPilesAi.class);
|
||||
apiToClass.put(ApiType.Unattach, CannotPlayAi.class);
|
||||
apiToClass.put(ApiType.UnattachAll, UnattachAllAi.class);
|
||||
apiToClass.put(ApiType.Untap, UntapAi.class);
|
||||
apiToClass.put(ApiType.UntapAll, UntapAllAi.class);
|
||||
apiToClass.put(ApiType.WinsGame, GameWinAi.class);
|
||||
|
||||
apiToClass.put(ApiType.InternalEtbReplacement, CanPlayAsDrawbackAi.class);
|
||||
apiToClass.put(ApiType.InternalLegendaryRule, LegendaryRuleAi.class);
|
||||
apiToClass.put(ApiType.InternalHaunt, HauntAi.class);
|
||||
}
|
||||
|
||||
public SpellAbilityAi get(ApiType api) {
|
||||
SpellAbilityAi result = apiToInstance.get(api);
|
||||
if( null == result ) {
|
||||
Class<? extends SpellAbilityAi> clz = apiToClass.get(api);
|
||||
if(null == clz) {
|
||||
System.err.println("No AI assigned for API: " + api);
|
||||
clz = CannotPlayAi.class;
|
||||
}
|
||||
result = ReflectionUtil.makeDefaultInstanceOf(clz);
|
||||
apiToInstance.put(api, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
18
forge-ai/src/main/java/forge/ai/ability/AddPhaseAi.java
Normal file
18
forge-ai/src/main/java/forge/ai/ability/AddPhaseAi.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class AddPhaseAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,16 +15,15 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.card.ability.AbilityUtils;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.card.spellability.Target;
|
||||
import forge.game.player.Player;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactory_Turns class.
|
||||
@@ -39,27 +38,26 @@ public class AddTurnAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Target tgt = sa.getTarget();
|
||||
|
||||
if (sa.getTarget() != null) {
|
||||
tgt.resetTargets();
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(ai)) {
|
||||
sa.getTarget().addTarget(ai);
|
||||
sa.getTargets().add(ai);
|
||||
} else if (mandatory) {
|
||||
for (final Player ally : ai.getAllies()) {
|
||||
if (sa.canTarget(ally)) {
|
||||
sa.getTarget().addTarget(ally);
|
||||
sa.getTargets().add(ally);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!sa.getTarget().isMinTargetsChosen(sa.getSourceCard(), sa) && sa.canTarget(opp)) {
|
||||
sa.getTarget().addTarget(opp);
|
||||
if (!sa.getTargetRestrictions().isMinTargetsChosen(sa.getHostCard(), sa) && sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final List<Player> tgtPlayers = AbilityUtils.getDefinedPlayers(sa.getSourceCard(), sa.getParam("Defined"), sa);
|
||||
final List<Player> tgtPlayers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
for (final Player p : tgtPlayers) {
|
||||
if (p.isOpponentOf(ai) && !mandatory) {
|
||||
return false;
|
||||
16
forge-ai/src/main/java/forge/ai/ability/AlwaysPlayAi.java
Normal file
16
forge-ai/src/main/java/forge/ai/ability/AlwaysPlayAi.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class AlwaysPlayAi extends SpellAbilityAi {
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
package forge.card.ability.ai;
|
||||
|
||||
import java.util.List;
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.Card;
|
||||
import forge.CardPredicates;
|
||||
import forge.card.ability.AbilityUtils;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.card.spellability.Target;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactoryAnimate class.
|
||||
@@ -31,9 +31,10 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
final Target tgt = sa.getTarget();
|
||||
final Card source = sa.getSourceCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = aiPlayer.getGame();
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
|
||||
// TODO - add some kind of check to answer
|
||||
// "Am I going to attack with this?"
|
||||
@@ -42,8 +43,8 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
|
||||
// don't use instant speed animate abilities outside computers
|
||||
// Combat_Begin step
|
||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_BEGIN)
|
||||
&& game.getPhaseHandler().isPlayerTurn(aiPlayer)
|
||||
if (!ph.is(PhaseType.COMBAT_BEGIN)
|
||||
&& ph.isPlayerTurn(aiPlayer)
|
||||
&& !SpellAbilityAi.isSorcerySpeed(sa)
|
||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
|
||||
return false;
|
||||
@@ -51,7 +52,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
|
||||
Player opponent = aiPlayer.getWeakestOpponent();
|
||||
// don't animate if the AI won't attack anyway
|
||||
if (game.getPhaseHandler().isPlayerTurn(aiPlayer)
|
||||
if (ph.isPlayerTurn(aiPlayer)
|
||||
&& aiPlayer.getLife() < 6
|
||||
&& opponent.getLife() > 6
|
||||
&& Iterables.any(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)) {
|
||||
@@ -60,14 +61,13 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
|
||||
// don't use instant speed animate abilities outside humans
|
||||
// Combat_Declare_Attackers_InstantAbility step
|
||||
if ((!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
|| (game.getCombat().getAttackers().isEmpty()))
|
||||
&& game.getPhaseHandler().isPlayerTurn(opponent)) {
|
||||
if (ph.getPlayerTurn().isOpponentOf(aiPlayer) &&
|
||||
(!ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS, opponent) || game.getCombat().getAttackersOf(aiPlayer).isEmpty())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't activate during main2 unless this effect is permanent
|
||||
if (game.getPhaseHandler().is(PhaseType.MAIN2) && !sa.hasParam("Permanent") && !sa.hasParam("UntilYourNextTurn")) {
|
||||
if (ph.is(PhaseType.MAIN2) && !sa.hasParam("Permanent") && !sa.hasParam("UntilYourNextTurn")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
boolean bFlag = false;
|
||||
if (sa.hasParam("AILogic")) {
|
||||
if ("EOT".equals(sa.getParam("AILogic"))) {
|
||||
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
if (ph.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
return false;
|
||||
} else {
|
||||
bFlag = true;
|
||||
@@ -112,7 +112,7 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
tgt.resetTargets();
|
||||
sa.resetTargets();
|
||||
if (!animateTgtAI(sa)) {
|
||||
return false;
|
||||
}
|
||||
@@ -125,8 +125,8 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
if (sa.getTarget() != null) {
|
||||
sa.getTarget().resetTargets();
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (!animateTgtAI(sa)) {
|
||||
return false;
|
||||
}
|
||||
@@ -140,18 +140,18 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
* animateTriggerAI.
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.card.spellability.SpellAbility} object.
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @param af
|
||||
* a {@link forge.card.ability.AbilityFactory} object.
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
if (sa.getTarget() != null && !animateTgtAI(sa) && !mandatory) {
|
||||
if (sa.usesTargeting() && !animateTgtAI(sa) && !mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -171,9 +171,9 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
* </p>
|
||||
*
|
||||
* @param af
|
||||
* a {@link forge.card.ability.AbilityFactory} object.
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
* @param sa
|
||||
* a {@link forge.card.spellability.SpellAbility} object.
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean animateTgtAI(final SpellAbility sa) {
|
||||
19
forge-ai/src/main/java/forge/ai/ability/AnimateAllAi.java
Normal file
19
forge-ai/src/main/java/forge/ai/ability/AnimateAllAi.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class AnimateAllAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false;
|
||||
} // end animateAllCanPlayAI()
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // end class AbilityFactoryAnimate
|
||||
@@ -1,39 +1,27 @@
|
||||
package forge.card.ability.ai;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.Card;
|
||||
import forge.CardLists;
|
||||
import forge.CardPredicates;
|
||||
import forge.CardUtil;
|
||||
import forge.card.ability.AbilityUtils;
|
||||
import forge.card.ability.ApiType;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.cardfactory.CardFactoryUtil;
|
||||
import forge.card.cost.Cost;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.card.spellability.Target;
|
||||
import forge.card.staticability.StaticAbility;
|
||||
import forge.game.ai.ComputerUtil;
|
||||
import forge.game.ai.ComputerUtilCard;
|
||||
import forge.game.ai.ComputerUtilCost;
|
||||
import forge.game.ai.ComputerUtilMana;
|
||||
import forge.game.phase.CombatUtil;
|
||||
import forge.ai.*;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -43,7 +31,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Random r = MyRandom.getRandom();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getSourceCard();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for these costs
|
||||
@@ -59,9 +47,9 @@ public class AttachAi extends SpellAbilityAi {
|
||||
final boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
// Attach spells always have a target
|
||||
final Target tgt = sa.getTarget();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
tgt.resetTargets();
|
||||
sa.resetTargets();
|
||||
if (!attachPreference(sa, tgt, false)) {
|
||||
return false;
|
||||
}
|
||||
@@ -166,7 +154,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
String type = "";
|
||||
|
||||
for (final StaticAbility stAb : attachSource.getStaticAbilities()) {
|
||||
final HashMap<String, String> stab = stAb.getMapParams();
|
||||
final Map<String, String> stab = stAb.getMapParams();
|
||||
if (stab.get("Mode").equals("Continuous") && stab.containsKey("AddType")) {
|
||||
type = stab.get("AddType");
|
||||
}
|
||||
@@ -331,7 +319,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
final Card attachSource) {
|
||||
// AI For choosing a Card to Animate.
|
||||
List<Card> betterList = CardLists.getNotType(list, "Creature");
|
||||
if (sa.getSourceCard().getName().equals("Animate Artifact")) {
|
||||
if (sa.getHostCard().getName().equals("Animate Artifact")) {
|
||||
betterList = CardLists.filter(betterList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
@@ -397,7 +385,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
// I know this isn't much better than Hardcoding, but some cards need it for now
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
Card chosen = null;
|
||||
if ("Guilty Conscience".equals(sa.getSourceCard().getName())) {
|
||||
if ("Guilty Conscience".equals(sa.getHostCard().getName())) {
|
||||
List<Card> aiStuffies = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
@@ -454,7 +442,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
final Card attachSource) {
|
||||
// AI For choosing a Card to Gain Control of.
|
||||
|
||||
if (sa.getTarget().canTgtPermanent()) {
|
||||
if (sa.getTargetRestrictions().canTgtPermanent()) {
|
||||
// If can target all Permanents, and Life isn't in eminent danger,
|
||||
// grab Planeswalker first, then Creature
|
||||
// if Life < 5 grab Creature first, then Planeswalker. Lands,
|
||||
@@ -561,7 +549,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
Card c = null;
|
||||
if ((prefList == null) || prefList.isEmpty()) {
|
||||
if (prefList == null || prefList.isEmpty()) {
|
||||
prefList = new ArrayList<Card>(list);
|
||||
} else {
|
||||
c = ComputerUtilCard.getBestAI(prefList);
|
||||
@@ -586,6 +574,20 @@ public class AttachAi extends SpellAbilityAi {
|
||||
});
|
||||
}
|
||||
|
||||
//some auras aren't useful in multiples
|
||||
if (attachSource.hasSVar("NonStackingAttachEffect")) {
|
||||
prefList = CardLists.filter(prefList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
for (Card aura : c.getEnchantedBy()) {
|
||||
if (aura.getName().equals(attachSource.getName()))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
c = ComputerUtilCard.getBestAI(prefList);
|
||||
|
||||
if (c == null) {
|
||||
@@ -609,14 +611,15 @@ public class AttachAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final Card card = sa.getSourceCard();
|
||||
final Card card = sa.getHostCard();
|
||||
// Check if there are any valid targets
|
||||
ArrayList<Object> targets = new ArrayList<Object>();
|
||||
final Target tgt = sa.getTarget();
|
||||
List<GameObject> targets = new ArrayList<GameObject>();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt == null) {
|
||||
targets = AbilityUtils.getDefinedObjects(sa.getSourceCard(), sa.getParam("Defined"), sa);
|
||||
targets = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
} else {
|
||||
AttachAi.attachPreference(sa, tgt, mandatory);
|
||||
targets = sa.getTargets().getTargets();
|
||||
}
|
||||
|
||||
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
|
||||
@@ -632,6 +635,13 @@ public class AttachAi extends SpellAbilityAi {
|
||||
if (ComputerUtilCard.evaluateCreature(oldTarget) > ComputerUtilCard.evaluateCreature(newTarget)) {
|
||||
return false;
|
||||
}
|
||||
// don't equip creatures that don't gain anything
|
||||
if (card.hasSVar("NonStackingAttachEffect")) {
|
||||
for (Card equipment : newTarget.getEquippedBy()) {
|
||||
if (equipment.getName().equals(card.getName()))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -677,8 +687,8 @@ public class AttachAi extends SpellAbilityAi {
|
||||
* the mandatory
|
||||
* @return true, if successful
|
||||
*/
|
||||
private static boolean attachPreference(final SpellAbility sa, final Target tgt, final boolean mandatory) {
|
||||
Object o;
|
||||
private static boolean attachPreference(final SpellAbility sa, final TargetRestrictions tgt, final boolean mandatory) {
|
||||
GameObject o;
|
||||
if (tgt.canTgtPlayer()) {
|
||||
o = attachToPlayerAIPreferences(sa.getActivatingPlayer(), sa, mandatory);
|
||||
} else {
|
||||
@@ -689,7 +699,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
tgt.addTarget(o);
|
||||
sa.getTargets().add(o);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -712,7 +722,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
Card c = null;
|
||||
List<Card> magnetList = null;
|
||||
String stCheck = null;
|
||||
if (attachSource.isAura()) {
|
||||
if (attachSource.isAura() || sa.hasParam("Bestow")) {
|
||||
stCheck = "EnchantedBy";
|
||||
magnetList = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
@@ -736,21 +746,47 @@ public class AttachAi extends SpellAbilityAi {
|
||||
return sVar.equals("Multiple") || (sVar.equals("Once") && !c.isEquipped());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ((magnetList != null) && !magnetList.isEmpty()) {
|
||||
// Always choose something from the Magnet List.
|
||||
// Probably want to "weight" the list by amount of Enchantments and
|
||||
// choose the "lightest"
|
||||
|
||||
magnetList = CardLists.filter(magnetList, new Predicate<Card>() {
|
||||
} else if (attachSource.isFortification()) {
|
||||
stCheck = "FortifiedBy";
|
||||
magnetList = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return CombatUtil.canAttack(c, ai.getWeakestOpponent());
|
||||
return c.isCreature() && !c.isFortified();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return ComputerUtilCard.getBestAI(magnetList);
|
||||
if (magnetList != null) {
|
||||
|
||||
// Look for Heroic triggers
|
||||
if (magnetList.isEmpty() && sa.isSpell()) {
|
||||
for (Card target : list) {
|
||||
for (Trigger t : target.getTriggers()) {
|
||||
if (t.getMode() == TriggerType.SpellCast) {
|
||||
final Map<String, String> params = t.getMapParams();
|
||||
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))) {
|
||||
magnetList.add(target);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!magnetList.isEmpty()) {
|
||||
// Always choose something from the Magnet List.
|
||||
// Probably want to "weight" the list by amount of Enchantments and
|
||||
// choose the "lightest"
|
||||
|
||||
magnetList = CardLists.filter(magnetList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return CombatUtil.canAttack(c, ai.getWeakestOpponent());
|
||||
}
|
||||
});
|
||||
|
||||
return ComputerUtilCard.getBestAI(magnetList);
|
||||
}
|
||||
}
|
||||
|
||||
int totToughness = 0;
|
||||
@@ -761,7 +797,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
|
||||
final Map<String, String> stabMap = stAbility.getMapParams();
|
||||
|
||||
if (!stabMap.get("Mode").equals("Continuous")) {
|
||||
if (!"Continuous".equals(stabMap.get("Mode"))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -809,7 +845,30 @@ public class AttachAi extends SpellAbilityAi {
|
||||
prefList = CardLists.filter(prefList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return containsUsefulKeyword(keywords, c, sa, pow);
|
||||
for (final String keyword : keywords) {
|
||||
if (isUsefulAttachKeyword(keyword, c, sa, pow)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//some auras/equipments aren't useful in multiples
|
||||
if (attachSource.hasSVar("NonStackingAttachEffect")) {
|
||||
prefList = CardLists.filter(prefList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
for (Card equipment : c.getEquippedBy()) {
|
||||
if (equipment.getName().equals(attachSource.getName()))
|
||||
return false;
|
||||
}
|
||||
for (Card aura : c.getEnchantedBy()) {
|
||||
if (aura.getName().equals(attachSource.getName()))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -841,6 +900,11 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
c = ComputerUtilCard.getBestAI(prefList);
|
||||
} else {
|
||||
for (Card pref : prefList) {
|
||||
if (pref.isLand() && pref.isUntapped()) {
|
||||
return pref;
|
||||
}
|
||||
}
|
||||
// If we grant abilities, we may want to put it on something Weak?
|
||||
// Possibly more defensive?
|
||||
c = ComputerUtilCard.getWorstPermanentAI(prefList, false, false, false, false);
|
||||
@@ -865,13 +929,17 @@ public class AttachAi extends SpellAbilityAi {
|
||||
* @return the card
|
||||
*/
|
||||
private static Card attachToCardAIPreferences(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
final Target tgt = sa.getTarget();
|
||||
final Card attachSource = sa.getSourceCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card attachSource = sa.getHostCard();
|
||||
// TODO AttachSource is currently set for the Source of the Spell, but
|
||||
// at some point can support attaching a different card
|
||||
|
||||
// Don't equip if already equipping
|
||||
if (attachSource.getEquippingCard() != null && attachSource.getEquippingCard().getController() == aiPlayer) {
|
||||
if (attachSource.getEquippingCard() != null && attachSource.getEquippingCard().getController() == aiPlayer || attachSource.hasSVar("DontEquip")) {
|
||||
return null;
|
||||
}
|
||||
// Don't fortify if already fortifying
|
||||
if (attachSource.getFortifyingCard() != null && attachSource.getFortifyingCard().getController() == aiPlayer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -882,7 +950,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
// I believe this is the only case where mandatory will be true, so just
|
||||
// check that when starting that work
|
||||
// But we shouldn't attach to things with Protection
|
||||
if (tgt.getZone().contains(ZoneType.Battlefield) && !mandatory) {
|
||||
if (!mandatory) {
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
} else {
|
||||
list = CardLists.filter(list, Predicates.not(CardPredicates.isProtectedFrom(attachSource)));
|
||||
@@ -969,25 +1037,6 @@ public class AttachAi extends SpellAbilityAi {
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains useful keyword.
|
||||
*
|
||||
* @param keywords
|
||||
* the keywords
|
||||
* @param card
|
||||
* the card
|
||||
* @param sa SpellAbility
|
||||
* @return true, if successful
|
||||
*/
|
||||
private static boolean containsUsefulKeyword(final ArrayList<String> keywords, final Card card, final SpellAbility sa, final int powerBonus) {
|
||||
for (final String keyword : keywords) {
|
||||
if (isUsefulAttachKeyword(keyword, card, sa, powerBonus)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains useful curse keyword.
|
||||
*
|
||||
@@ -1018,21 +1067,23 @@ public class AttachAi extends SpellAbilityAi {
|
||||
* @return true, if is useful keyword
|
||||
*/
|
||||
private static boolean isUsefulAttachKeyword(final String keyword, final Card card, final SpellAbility sa, final int powerBonus) {
|
||||
final PhaseHandler ph = sa.getActivatingPlayer().getGame().getPhaseHandler();
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final Player opponent = ai.getOpponent();
|
||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
|
||||
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
|
||||
return false;
|
||||
}
|
||||
final boolean evasive = (keyword.equals("Unblockable") || keyword.equals("Fear")
|
||||
|| keyword.equals("Intimidate") || keyword.equals("Shadow")
|
||||
|| keyword.equals("Flying") || keyword.equals("Horsemanship")
|
||||
|| keyword.endsWith("walk") || keyword.equals("CARDNAME can't be blocked except by Walls.")
|
||||
|| keyword.equals("All creatures able to block CARDNAME do so.")
|
||||
|| keyword.equals("CARDNAME can't be blocked by more than one creature."));
|
||||
|| keyword.endsWith("walk") || keyword.startsWith("CantBeBlockedBy")
|
||||
|| keyword.equals("All creatures able to block CARDNAME do so."));
|
||||
// give evasive keywords to creatures that can attack and deal damage
|
||||
if (evasive) {
|
||||
if (card.getNetCombatDamage() + powerBonus <= 0
|
||||
|| !CombatUtil.canAttackNextTurn(card)
|
||||
|| !CombatUtil.canBeBlocked(card)) {
|
||||
|| !CombatUtil.canBeBlocked(card, opponent)) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.equals("Haste")) {
|
||||
@@ -1047,7 +1098,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
return true;
|
||||
} else if (keyword.endsWith("Deathtouch") || keyword.endsWith("Wither")) {
|
||||
if (card.getNetCombatDamage() + powerBonus <= 0
|
||||
|| ((!CombatUtil.canBeBlocked(card) || !CombatUtil.canAttackNextTurn(card))
|
||||
|| ((!CombatUtil.canBeBlocked(card, opponent) || !CombatUtil.canAttackNextTurn(card))
|
||||
&& !CombatUtil.canBlock(card, true))) {
|
||||
return false;
|
||||
}
|
||||
@@ -1063,17 +1114,17 @@ public class AttachAi extends SpellAbilityAi {
|
||||
} else if (keyword.startsWith("Flanking")) {
|
||||
if (card.getNetCombatDamage() + powerBonus <= 0
|
||||
|| !CombatUtil.canAttackNextTurn(card)
|
||||
|| !CombatUtil.canBeBlocked(card)) {
|
||||
|| !CombatUtil.canBeBlocked(card, opponent)) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.startsWith("Bushido")) {
|
||||
if ((!CombatUtil.canBeBlocked(card) || !CombatUtil.canAttackNextTurn(card))
|
||||
if ((!CombatUtil.canBeBlocked(card, opponent) || !CombatUtil.canAttackNextTurn(card))
|
||||
&& !CombatUtil.canBlock(card, true)) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.equals("Trample")) {
|
||||
if (card.getNetCombatDamage() + powerBonus <= 1
|
||||
|| !CombatUtil.canBeBlocked(card)
|
||||
|| !CombatUtil.canBeBlocked(card, opponent)
|
||||
|| !CombatUtil.canAttackNextTurn(card)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1093,17 +1144,20 @@ public class AttachAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.endsWith("CARDNAME can block an additional creature.")) {
|
||||
if (!CombatUtil.canBlock(card, true) || card.hasKeyword("CARDNAME can block any number of creatures.")) {
|
||||
if (!CombatUtil.canBlock(card, true) || card.hasKeyword("CARDNAME can block any number of creatures.")
|
||||
|| card.hasKeyword("CARDNAME can block an additional ninety-nine creatures.")) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.equals("CARDNAME can attack as though it didn't have defender.")) {
|
||||
if (!card.hasKeyword("Defender") || card.hasKeyword("CARDNAME can attack as though it didn't have defender.")) {
|
||||
if (!card.hasKeyword("Defender") || card.getNetCombatDamage() + powerBonus <= 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.equals("Shroud") || keyword.equals("Hexproof")) {
|
||||
if (card.hasKeyword("Shroud") || card.hasKeyword("Hexproof")) {
|
||||
return false;
|
||||
}
|
||||
} else if (keyword.equals("Defender")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1167,12 +1221,12 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, List<Card> options, boolean isOptional) {
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
return attachToCardAIPreferences(ai, sa, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, List<Player> options) {
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> options) {
|
||||
return attachToPlayerAIPreferences(ai, sa, true);
|
||||
}
|
||||
}
|
||||
52
forge-ai/src/main/java/forge/ai/ability/BalanceAi.java
Normal file
52
forge-ai/src/main/java/forge/ai/ability/BalanceAi.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BalanceAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
String logic = sa.getParam("AILogic");
|
||||
|
||||
int diff = 0;
|
||||
// TODO Add support for multiplayer logic
|
||||
final Player opp = aiPlayer.getOpponent();
|
||||
final List<Card> humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
||||
final List<Card> compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
if ("BalanceCreaturesAndLands".equals(logic)) {
|
||||
// Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting
|
||||
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
|
||||
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
|
||||
diff += 1.5 * ( CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
|
||||
CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size());
|
||||
} else if ("BalancePermanents".equals(logic)) {
|
||||
// Don't cast if you have to sacrifice permanents
|
||||
diff += humPerms.size() - compPerms.size();
|
||||
}
|
||||
|
||||
if (diff < 0) {
|
||||
// Don't sacrifice permanents even if opponent has a ton of cards in hand
|
||||
return false;
|
||||
}
|
||||
|
||||
final List<Card> humHand = opp.getCardsIn(ZoneType.Hand);
|
||||
final List<Card> compHand = aiPlayer.getCardsIn(ZoneType.Hand);
|
||||
diff += 0.5 * (humHand.size() - compHand.size());
|
||||
|
||||
// Larger differential == more chance to actually cast this spell
|
||||
return diff > 2 && MyRandom.getRandom().nextInt(100) < diff*10;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BecomesBlockedAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Game game = aiPlayer.getGame();
|
||||
|
||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
|| !game.getPhaseHandler().getPlayerTurn().isOpponentOf(aiPlayer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
List<Card> list = game.getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.filterControlledBy(list, aiPlayer.getOpponents());
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source);
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
list = CardLists.getNotKeyword(list, "Trample");
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
Card choice = null;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
return false;
|
||||
}
|
||||
|
||||
list.remove(choice);
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
|
||||
// TODO - implement AI
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
boolean chance;
|
||||
|
||||
// TODO - implement AI
|
||||
chance = false;
|
||||
|
||||
return chance;
|
||||
}
|
||||
}
|
||||
58
forge-ai/src/main/java/forge/ai/ability/BondAi.java
Normal file
58
forge-ai/src/main/java/forge/ai/ability/BondAi.java
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2011 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactoryBond class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
|
||||
*/
|
||||
public final class BondAi extends SpellAbilityAi {
|
||||
/**
|
||||
* <p>
|
||||
* bondCanPlayAI.
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param af
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return true;
|
||||
} // end bondCanPlayAI()
|
||||
|
||||
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
return ComputerUtilCard.getBestCreatureAI(options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CanPlayAsDrawbackAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* copySpellTriggerAI.
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @param af
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
// This might be called from CopySpellAbilityEffect - to hide warning (for having no overload) use this simple overload
|
||||
return spells.get(0);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class CannotPlayAi extends SpellAbilityAi {
|
||||
/* (non-Javadoc)
|
||||
26
forge-ai/src/main/java/forge/ai/ability/ChangeTargetsAi.java
Normal file
26
forge-ai/src/main/java/forge/ai/ability/ChangeTargetsAi.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class ChangeTargetsAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI
|
||||
* (forge.game.player.Player, java.util.Map,
|
||||
* forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
1212
forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
Normal file
1212
forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,22 +1,22 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import forge.Card;
|
||||
import forge.CardLists;
|
||||
import forge.card.ability.AbilityUtils;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.cost.Cost;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.card.spellability.Target;
|
||||
import forge.game.ai.ComputerUtilCard;
|
||||
import forge.game.ai.ComputerUtilCost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -26,9 +26,9 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
// Change Zone All, can be any type moving from one zone to another
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getSourceCard();
|
||||
final Card source = sa.getHostCard();
|
||||
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||
final ZoneType origin = ZoneType.smartValueOf(sa.getParam("Origin"));
|
||||
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for these costs
|
||||
@@ -57,7 +57,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
final List<Card> humanType = AbilityUtils.filterListByType(opp.getCardsIn(origin), sa.getParam("ChangeType"), sa);
|
||||
List<Card> computerType = ai.getCardsIn(origin);
|
||||
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
|
||||
final Target tgt = sa.getTarget();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
// TODO improve restrictions on when the AI would want to use this
|
||||
// spBounceAll has some AI we can compare to.
|
||||
@@ -67,8 +67,8 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
|| !opp.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
}
|
||||
tgt.resetTargets();
|
||||
tgt.addTarget(opp);
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Battlefield)) {
|
||||
// this statement is assuming the AI is trying to use this spell
|
||||
@@ -82,8 +82,8 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
|| !opp.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
}
|
||||
tgt.resetTargets();
|
||||
tgt.addTarget(opp);
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
computerType.clear();
|
||||
}
|
||||
if ((CardLists.getNotType(humanType, "Creature").size() == 0) && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
|
||||
@@ -108,8 +108,8 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
|| !opp.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
}
|
||||
tgt.resetTargets();
|
||||
tgt.addTarget(opp);
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Exile)) {
|
||||
|
||||
@@ -159,9 +159,9 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
* changeZoneAllPlayDrawbackAI.
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.card.spellability.SpellAbility} object.
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param af
|
||||
* a {@link forge.card.ability.AbilityFactory} object.
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@@ -189,14 +189,14 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
// TODO improve restrictions on when the AI would want to use this
|
||||
// spBounceAll has some AI we can compare to.
|
||||
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
|
||||
final Target tgt = sa.getTarget();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
if (opp.getCardsIn(ZoneType.Hand).isEmpty()
|
||||
|| !opp.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
}
|
||||
tgt.resetTargets();
|
||||
tgt.addTarget(opp);
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Battlefield)) {
|
||||
// this statement is assuming the AI is trying to use this spell offensively
|
||||
@@ -215,14 +215,14 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Graveyard)) {
|
||||
final Target tgt = sa.getTarget();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
if (opp.getCardsIn(ZoneType.Graveyard).isEmpty()
|
||||
|| !opp.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
}
|
||||
tgt.resetTargets();
|
||||
tgt.addTarget(opp);
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
} else if (origin.equals(ZoneType.Exile)) {
|
||||
|
||||
99
forge-ai/src/main/java/forge/ai/ability/CharmAi.java
Normal file
99
forge-ai/src/main/java/forge/ai/ability/CharmAi.java
Normal file
@@ -0,0 +1,99 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiPlayDecision;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class CharmAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Random r = MyRandom.getRandom();
|
||||
|
||||
final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
|
||||
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
|
||||
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
|
||||
|
||||
|
||||
List<AbilitySub> chosenList = chooseOptionsAi(sa, ai, timingRight, num, min, false);
|
||||
|
||||
if (chosenList.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
return r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
}
|
||||
|
||||
public static List<AbilitySub> chooseOptionsAi(SpellAbility sa, final Player ai, boolean playNow, int num, int min, boolean opponentChoser) {
|
||||
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
|
||||
List<AbilitySub> chosenList = new ArrayList<AbilitySub>();
|
||||
|
||||
if (opponentChoser) {
|
||||
// This branch is for "An Opponent chooses" Charm spells from Alliances
|
||||
// Current just choose the first available spell, which seem generally less disastrous for the AI.
|
||||
//return choices.subList(0, 1);
|
||||
return choices.subList(1, choices.size());
|
||||
}
|
||||
|
||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
for (int i = 0; i < num; i++) {
|
||||
AbilitySub thisPick = null;
|
||||
for (SpellAbility sub : choices) {
|
||||
sub.setActivatingPlayer(ai);
|
||||
if (!playNow && AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
|
||||
thisPick = (AbilitySub) sub;
|
||||
choices.remove(sub);
|
||||
playNow = true;
|
||||
break;
|
||||
}
|
||||
if ((playNow || i < num - 1) && aic.doTrigger(sub, false)) {
|
||||
thisPick = (AbilitySub) sub;
|
||||
choices.remove(sub);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (thisPick != null) {
|
||||
chosenList.add(thisPick);
|
||||
}
|
||||
}
|
||||
if (playNow && chosenList.size() < min) {
|
||||
for (int i = 0; i < min; i++) {
|
||||
AbilitySub thisPick = null;
|
||||
for (SpellAbility sub : choices) {
|
||||
sub.setActivatingPlayer(ai);
|
||||
if (aic.doTrigger(sub, true)) {
|
||||
thisPick = (AbilitySub) sub;
|
||||
choices.remove(sub);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (thisPick != null) {
|
||||
chosenList.add(thisPick);
|
||||
}
|
||||
}
|
||||
}
|
||||
return chosenList;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> opponents) {
|
||||
return Aggregates.random(opponents);
|
||||
}
|
||||
|
||||
}
|
||||
188
forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java
Normal file
188
forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java
Normal file
@@ -0,0 +1,188 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ChooseCardAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(ai.getOpponent())) {
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("AILogic")) {
|
||||
ZoneType choiceZone = ZoneType.Battlefield;
|
||||
String logic = sa.getParam("AILogic");
|
||||
if (sa.hasParam("ChoiceZone")) {
|
||||
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
|
||||
}
|
||||
List<Card> choices = ai.getGame().getCardsIn(choiceZone);
|
||||
if (sa.hasParam("Choices")) {
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||
}
|
||||
if (sa.hasParam("TargetControls")) {
|
||||
choices = CardLists.filterControlledBy(choices, ai.getOpponent());
|
||||
}
|
||||
if (logic.equals("AtLeast1") || logic.equals("OppPreferred")) {
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (logic.equals("AtLeast2") || logic.equals("BestBlocker")) {
|
||||
if (choices.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
} else if (logic.equals("Clone")) {
|
||||
choices = CardLists.getValidCards(choices, "Permanent.YouDontCtrl,Permanent.nonLegendary", host.getController(), host);
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (logic.equals("Never")) {
|
||||
return false;
|
||||
} else if (logic.equals("NeedsPrevention")) {
|
||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
final Combat combat = game.getCombat();
|
||||
choices = CardLists.filter(choices, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (!combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||
return false;
|
||||
}
|
||||
int ref = host.getName().equals("Forcefield") ? 1 : 0;
|
||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > ref;
|
||||
}
|
||||
});
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
final Card host = sa.getHostCard();
|
||||
final String logic = sa.getParam("AILogic");
|
||||
Card choice = null;
|
||||
if (logic == null) {
|
||||
// Base Logic is choose "best"
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
} else if ("WorstCard".equals(logic)) {
|
||||
choice = ComputerUtilCard.getWorstAI(options);
|
||||
} else if (logic.equals("BestBlocker")) {
|
||||
if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) {
|
||||
options = CardLists.filter(options, Presets.UNTAPPED);
|
||||
}
|
||||
choice = ComputerUtilCard.getBestCreatureAI(options);
|
||||
} else if (logic.equals("Clone")) {
|
||||
if (!CardLists.getValidCards(options, "Permanent.YouDontCtrl,Permanent.nonLegendary", host.getController(), host).isEmpty()) {
|
||||
options = CardLists.getValidCards(options, "Permanent.YouDontCtrl,Permanent.nonLegendary", host.getController(), host);
|
||||
}
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
} else if (logic.equals("Untap")) {
|
||||
if (!CardLists.getValidCards(options, "Permanent.YouCtrl,Permanent.tapped", host.getController(), host).isEmpty()) {
|
||||
options = CardLists.getValidCards(options, "Permanent.YouCtrl,Permanent.tapped", host.getController(), host);
|
||||
}
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
} else if (logic.equals("NeedsPrevention")) {
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
List<Card> better = CardLists.filter(options, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||
return false;
|
||||
}
|
||||
int ref = host.getName().equals("Forcefield") ? 1 : 0;
|
||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > ref;
|
||||
}
|
||||
});
|
||||
if (!better.isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestAI(better);
|
||||
} else {
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
} else if ("OppPreferred".equals(logic)) {
|
||||
List<Card> oppControlled = CardLists.filterControlledBy(options, ai.getOpponents());
|
||||
if (!oppControlled.isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestAI(oppControlled);
|
||||
} else {
|
||||
List<Card> aiControlled = CardLists.filterControlledBy(options, ai);
|
||||
choice = ComputerUtilCard.getWorstAI(aiControlled);
|
||||
}
|
||||
} else if ("LowestCMCCreature".equals(logic)) {
|
||||
List<Card> creats = CardLists.filter(options, Presets.CREATURES);
|
||||
creats = CardLists.filterToughness(creats, 1);
|
||||
if (creats.isEmpty()) {
|
||||
choice = ComputerUtilCard.getWorstAI(options);
|
||||
} else {
|
||||
CardLists.sortByCmcDesc(creats);
|
||||
Collections.reverse(creats);
|
||||
choice = creats.get(0);
|
||||
}
|
||||
} else if ("TangleWire".equals(logic)) {
|
||||
List<Card> betterList = CardLists.filter(options, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (c.isCreature()) {
|
||||
return false;
|
||||
}
|
||||
for (SpellAbility sa : c.getAllSpellAbilities()) {
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
System.out.println("Tangle Wire" + options + " - " + betterList);
|
||||
if (!betterList.isEmpty()) {
|
||||
choice = betterList.get(0);
|
||||
} else {
|
||||
choice = ComputerUtilCard.getWorstPermanentAI(options, false, false, false, false);
|
||||
}
|
||||
} else {
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
return choice;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
public class ChooseCardNameAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
Card source = sa.getHostCard();
|
||||
if (sa.hasParam("AILogic")) {
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String logic = sa.getParam("AILogic");
|
||||
if (logic.equals("MomirAvatar")) {
|
||||
if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN1)) {
|
||||
return false;
|
||||
}
|
||||
// Set PayX here to maximum value.
|
||||
int tokenSize = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
|
||||
// Some basic strategy for Momir
|
||||
if (tokenSize < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tokenSize > 11) {
|
||||
tokenSize = 11;
|
||||
}
|
||||
|
||||
source.setSVar("PayX", Integer.toString(tokenSize));
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
} else {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
// TODO - there is no AILogic implemented yet
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class ChooseColorAi extends SpellAbilityAi {
|
||||
@@ -0,0 +1,36 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
return canPlayAI(aiPlayer, sa);
|
||||
}
|
||||
|
||||
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
|
||||
if ("Random".equals(sa.getParam("AILogic"))) {
|
||||
return Aggregates.random(spells);
|
||||
} else {
|
||||
return spells.get(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
87
forge-ai/src/main/java/forge/ai/ability/ChoosePlayerAi.java
Normal file
87
forge-ai/src/main/java/forge/ai/ability/ChoosePlayerAi.java
Normal file
@@ -0,0 +1,87 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class ChoosePlayerAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> choices) {
|
||||
Player chosen = null;
|
||||
if ("Curse".equals(sa.getParam("AILogic"))) {
|
||||
for (Player pc : choices) {
|
||||
if (pc.isOpponentOf(ai)) {
|
||||
chosen = pc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (chosen == null) {
|
||||
chosen = Iterables.getFirst(choices, null);
|
||||
System.out.println("No good curse choices. Picking first available: " + chosen);
|
||||
}
|
||||
} else if ("Pump".equals(sa.getParam("AILogic"))) {
|
||||
chosen = choices.contains(ai) ? ai : Iterables.getFirst(choices, null);
|
||||
} else if ("BestAllyBoardPosition".equals(sa.getParam("AILogic"))) {
|
||||
List<Player> prefChoices = Lists.newArrayList(choices);
|
||||
prefChoices.removeAll(ai.getOpponents());
|
||||
if (!prefChoices.isEmpty()) {
|
||||
chosen = ComputerUtil.evaluateBoardPosition(prefChoices);
|
||||
}
|
||||
if (chosen == null) {
|
||||
chosen = Iterables.getFirst(choices, null);
|
||||
System.out.println("No good curse choices. Picking first available: " + chosen);
|
||||
}
|
||||
} else if ("MostCardsInHand".equals(sa.getParam("AILogic"))) {
|
||||
int cardsInHand = 0;
|
||||
for (final Player p : choices) {
|
||||
int hand = p.getCardsIn(ZoneType.Hand).size();
|
||||
if (hand >= cardsInHand) {
|
||||
chosen = p;
|
||||
cardsInHand = hand;
|
||||
}
|
||||
}
|
||||
} else if ("LeastCreatures".equals(sa.getParam("AILogic"))) {
|
||||
int creats = 50;
|
||||
for (final Player p : choices) {
|
||||
int curr = p.getCreaturesInPlay().size();
|
||||
if (curr <= creats) {
|
||||
chosen = p;
|
||||
creats = curr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
System.out.println("Default player choice logic.");
|
||||
chosen = choices.contains(ai) ? ai : Iterables.getFirst(choices, null);
|
||||
}
|
||||
return chosen;
|
||||
}
|
||||
}
|
||||
186
forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java
Normal file
186
forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java
Normal file
@@ -0,0 +1,186 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class ChooseSourceAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||
// TODO: AI Support! Currently this is copied from AF ChooseCard.
|
||||
// When implementing AI, I believe AI also needs to be made aware of the damage sources chosen
|
||||
// to be prevented (e.g. so the AI doesn't attack with a creature that will not deal any damage
|
||||
// to the player because a CoP was pre-activated on it - unless, of course, there's another
|
||||
// possible reason to attack with that creature).
|
||||
final Card host = sa.getHostCard();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for these costs
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(ai.getOpponent())) {
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("AILogic")) {
|
||||
final Game game = ai.getGame();
|
||||
if (sa.getParam("AILogic").equals("NeedsPrevention")) {
|
||||
if (!game.getStack().isEmpty()) {
|
||||
final SpellAbility topStack = game.getStack().peekAbility();
|
||||
if (sa.hasParam("Choices") && !topStack.getHostCard().isValid(sa.getParam("Choices"), ai, source)) {
|
||||
return false;
|
||||
}
|
||||
final ApiType threatApi = topStack.getApi();
|
||||
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card threatSource = topStack.getHostCard();
|
||||
List<? extends GameObject> objects = getTargets(topStack);
|
||||
if (!topStack.usesTargeting() && topStack.hasParam("ValidPlayers") && !topStack.hasParam("Defined")) {
|
||||
objects = AbilityUtils.getDefinedPlayers(threatSource, topStack.getParam("ValidPlayers"), topStack);
|
||||
}
|
||||
|
||||
if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) {
|
||||
return false;
|
||||
}
|
||||
int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
|
||||
if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
|
||||
return false;
|
||||
}
|
||||
List<Card> choices = game.getCardsIn(ZoneType.Battlefield);
|
||||
if (sa.hasParam("Choices")) {
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
|
||||
}
|
||||
final Combat combat = game.getCombat();
|
||||
choices = CardLists.filter(choices, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||
return false;
|
||||
}
|
||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > 0;
|
||||
}
|
||||
});
|
||||
if (choices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final Game game = ai.getGame();
|
||||
if (!game.getStack().isEmpty()) {
|
||||
Card choseCard = chooseCardOnStack(sa, ai, game);
|
||||
if (choseCard != null) {
|
||||
return choseCard;
|
||||
}
|
||||
}
|
||||
|
||||
final Combat combat = game.getCombat();
|
||||
|
||||
List<Card> permanentSources = CardLists.filter(options, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (c == null || c.getZone() == null || c.getZone().getZoneType() != ZoneType.Battlefield
|
||||
|| combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
|
||||
return false;
|
||||
}
|
||||
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > 0;
|
||||
}
|
||||
});
|
||||
return ComputerUtilCard.getBestCreatureAI(permanentSources);
|
||||
|
||||
} else {
|
||||
return ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
}
|
||||
|
||||
private Card chooseCardOnStack(SpellAbility sa, Player ai, Game game) {
|
||||
for (SpellAbilityStackInstance si : game.getStack()) {
|
||||
final Card source = si.getSourceCard();
|
||||
final SpellAbility abilityOnStack = si.getSpellAbility();
|
||||
|
||||
if (sa.hasParam("Choices") && !abilityOnStack.getHostCard().isValid(sa.getParam("Choices"), ai, sa.getHostCard())) {
|
||||
continue;
|
||||
}
|
||||
final ApiType threatApi = abilityOnStack.getApi();
|
||||
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<? extends GameObject> objects = getTargets(abilityOnStack);
|
||||
|
||||
if (!abilityOnStack.usesTargeting() && !abilityOnStack.hasParam("Defined") && abilityOnStack.hasParam("ValidPlayers"))
|
||||
objects = AbilityUtils.getDefinedPlayers(source, abilityOnStack.getParam("ValidPlayers"), abilityOnStack);
|
||||
|
||||
if (!objects.contains(ai) || abilityOnStack.hasParam("NoPrevention")) {
|
||||
continue;
|
||||
}
|
||||
int dmg = AbilityUtils.calculateAmount(source, abilityOnStack.getParam("NumDmg"), abilityOnStack);
|
||||
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false) <= 0) {
|
||||
continue;
|
||||
}
|
||||
return source;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
33
forge-ai/src/main/java/forge/ai/ability/ChooseTypeAi.java
Normal file
33
forge-ai/src/main/java/forge/ai/ability/ChooseTypeAi.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class ChooseTypeAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
if (!sa.hasParam("AILogic")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return doTriggerAINoCost(aiPlayer, sa, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
for (final Player p : AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa)) {
|
||||
if (p.isOpponentOf(ai) && !mandatory) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
36
forge-ai/src/main/java/forge/ai/ability/ClashAi.java
Normal file
36
forge-ai/src/main/java/forge/ai/ability/ClashAi.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
public class ClashAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Player opp = ai.getOpponent();
|
||||
if (tgt != null) {
|
||||
if (!sa.canTarget(opp)) {
|
||||
return false;
|
||||
}
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +1,24 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.Card;
|
||||
import forge.card.ability.AbilityUtils;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.card.spellability.Target;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CloneAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Target tgt = sa.getTarget();
|
||||
final Card source = sa.getSourceCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = source.getGame();
|
||||
|
||||
boolean useAbility = true;
|
||||
@@ -45,9 +46,7 @@ public class CloneAi extends SpellAbilityAi {
|
||||
|
||||
// don't use instant speed clone abilities outside humans
|
||||
// Combat_Declare_Attackers_InstantAbility step
|
||||
if ((!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
|| game.getCombat().getAttackers().isEmpty())
|
||||
&& !phase.isPlayerTurn(ai)) {
|
||||
if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || phase.isPlayerTurn(ai) || game.getCombat().getAttackers().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -85,7 +84,7 @@ public class CloneAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
tgt.resetTargets();
|
||||
sa.resetTargets();
|
||||
useAbility &= cloneTgtAI(sa);
|
||||
}
|
||||
|
||||
@@ -97,7 +96,7 @@ public class CloneAi extends SpellAbilityAi {
|
||||
// AI should only activate this during Human's turn
|
||||
boolean chance = true;
|
||||
|
||||
if (sa.getTarget() != null) {
|
||||
if (sa.usesTargeting()) {
|
||||
chance = cloneTgtAI(sa);
|
||||
}
|
||||
|
||||
@@ -110,7 +109,7 @@ public class CloneAi extends SpellAbilityAi {
|
||||
|
||||
boolean chance = true;
|
||||
|
||||
if (sa.getTarget() != null) {
|
||||
if (sa.usesTargeting()) {
|
||||
chance = cloneTgtAI(sa);
|
||||
}
|
||||
|
||||
@@ -130,9 +129,9 @@ public class CloneAi extends SpellAbilityAi {
|
||||
* </p>
|
||||
*
|
||||
* @param af
|
||||
* a {@link forge.card.ability.AbilityFactory} object.
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
* @param sa
|
||||
* a {@link forge.card.spellability.SpellAbility} object.
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean cloneTgtAI(final SpellAbility sa) {
|
||||
@@ -143,5 +142,14 @@ public class CloneAi extends SpellAbilityAi {
|
||||
// good job of picking a good target
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
// Didn't confirm in the original code
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ControlExchangeAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, final SpellAbility sa) {
|
||||
Card object1 = null;
|
||||
Card object2 = null;
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
sa.resetTargets();
|
||||
|
||||
List<Card> list =
|
||||
CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard());
|
||||
// AI won't try to grab cards that are filtered out of AI decks on
|
||||
// purpose
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
final Map<String, String> vars = c.getSVars();
|
||||
return !vars.containsKey("RemAIDeck") && c.canBeTargetedBy(sa);
|
||||
}
|
||||
});
|
||||
object1 = ComputerUtilCard.getBestAI(list);
|
||||
if (sa.hasParam("Defined")) {
|
||||
object2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
|
||||
} else if (tgt.getMinTargets(sa.getHostCard(), sa) > 1) {
|
||||
List<Card> list2 = ai.getCardsIn(ZoneType.Battlefield);
|
||||
list2 = CardLists.getValidCards(list2, tgt.getValidTgts(), ai, sa.getHostCard());
|
||||
object2 = ComputerUtilCard.getWorstAI(list2);
|
||||
sa.getTargets().add(object2);
|
||||
}
|
||||
if (object1 == null || object2 == null) {
|
||||
return false;
|
||||
}
|
||||
if (ComputerUtilCard.evaluateCreature(object1) > ComputerUtilCard.evaluateCreature(object2) + 40) {
|
||||
sa.getTargets().add(object1);
|
||||
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.getTargetRestrictions() == null) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return canPlayAI(aiPlayer, sa);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
228
forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java
Normal file
228
forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2011 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
//AB:GainControl|ValidTgts$Creature|TgtPrompt$Select target legendary creature|LoseControl$Untap,LoseControl|SpellDescription$Gain control of target xxxxxxx
|
||||
|
||||
//GainControl specific sa:
|
||||
// LoseControl - the lose control conditions (as a comma separated list)
|
||||
// -Untap - source card becomes untapped
|
||||
// -LoseControl - you lose control of source card
|
||||
// -LeavesPlay - source card leaves the battlefield
|
||||
// -PowerGT - (not implemented yet for Old Man of the Sea)
|
||||
// AddKWs - Keywords to add to the controlled card
|
||||
// (as a "&"-separated list; like Haste, Sacrifice CARDNAME at EOT, any standard keyword)
|
||||
// OppChoice - set to True if opponent chooses creature (for Preacher) - not implemented yet
|
||||
// Untap - set to True if target card should untap when control is taken
|
||||
// DestroyTgt - actions upon which the tgt should be destroyed. same list as LoseControl
|
||||
// NoRegen - set if destroyed creature can't be regenerated. used only with DestroyTgt
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactory_GainControl class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: AbilityFactoryGainControl.java 17764 2012-10-29 11:04:18Z Sloth $
|
||||
*/
|
||||
public class ControlGainAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||
boolean hasCreature = false;
|
||||
boolean hasArtifact = false;
|
||||
boolean hasEnchantment = false;
|
||||
boolean hasLand = false;
|
||||
|
||||
final List<String> lose = sa.hasParam("LoseControl") ? Arrays.asList(sa.getParam("LoseControl").split(",")) : null;
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
Player opp = ai.getOpponent();
|
||||
|
||||
// if Defined, then don't worry about targeting
|
||||
if (tgt == null) {
|
||||
if (sa.hasParam("AllValid")) {
|
||||
List<Card> tgtCards = ai.getOpponent().getCardsIn(ZoneType.Battlefield);
|
||||
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
||||
if (tgtCards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
if (!opp.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
}
|
||||
if (tgt.isRandomTarget()) {
|
||||
sa.getTargets().add(Aggregates.random(tgt.getAllCandidates(sa, false)));
|
||||
} else {
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't steal something if I can't Attack without, or prevent it from
|
||||
// blocking at least
|
||||
if (lose != null && lose.contains("EOT")
|
||||
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& !sa.isTrigger()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Card> list =
|
||||
CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard());
|
||||
|
||||
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
final Map<String, String> vars = c.getSVars();
|
||||
if (!c.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
}
|
||||
if (sa.isTrigger()) {
|
||||
return true;
|
||||
}
|
||||
if (c.isCreature() && (!CombatUtil.canAttackNextTurn(c, ai.getOpponent()) || c.getNetCombatDamage() == 0)) {
|
||||
return false;
|
||||
}
|
||||
return !vars.containsKey("RemAIDeck");
|
||||
}
|
||||
});
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
Card t = null;
|
||||
for (final Card c : list) {
|
||||
if (c.isCreature()) {
|
||||
hasCreature = true;
|
||||
}
|
||||
if (c.isArtifact()) {
|
||||
hasArtifact = true;
|
||||
}
|
||||
if (c.isLand()) {
|
||||
hasLand = true;
|
||||
}
|
||||
if (c.isEnchantment()) {
|
||||
hasEnchantment = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (list.isEmpty()) {
|
||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCreature) {
|
||||
t = ComputerUtilCard.getBestCreatureAI(list);
|
||||
} else if (hasArtifact) {
|
||||
t = ComputerUtilCard.getBestArtifactAI(list);
|
||||
} else if (hasLand) {
|
||||
t = ComputerUtilCard.getBestLandAI(list);
|
||||
} else if (hasEnchantment) {
|
||||
t = ComputerUtilCard.getBestEnchantmentAI(list, sa, true);
|
||||
} else {
|
||||
t = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||
}
|
||||
|
||||
sa.getTargets().add(t);
|
||||
list.remove(t);
|
||||
|
||||
hasCreature = false;
|
||||
hasArtifact = false;
|
||||
hasLand = false;
|
||||
hasEnchantment = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.getTargetRestrictions() == null) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if(!this.canPlayAI(ai, sa) && mandatory) {
|
||||
List<Card> list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
} else {
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, final Player ai) {
|
||||
final Game game = ai.getGame();
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
if (sa.hasParam("AllValid")) {
|
||||
List<Card> tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponent());
|
||||
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
|
||||
if (tgtCards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final List<String> lose = sa.hasParam("LoseControl") ? Arrays.asList(sa.getParam("LoseControl").split(",")) : null;
|
||||
if ((lose != null) && lose.contains("EOT")
|
||||
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return this.canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // pumpDrawbackAI()
|
||||
}
|
||||
127
forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java
Normal file
127
forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CopyPermanentAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
// Card source = sa.getHostCard();
|
||||
// TODO - I'm sure someone can do this AI better
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) {
|
||||
return false;
|
||||
} else {
|
||||
return this.doTriggerAINoCost(aiPlayer, sa, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
// ////
|
||||
// Targeting
|
||||
|
||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||
|
||||
if (abTgt != null) {
|
||||
List<Card> list = aiPlayer.getGame().getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
final Map<String, String> vars = c.getSVars();
|
||||
return !vars.containsKey("RemAIDeck");
|
||||
}
|
||||
});
|
||||
sa.resetTargets();
|
||||
// target loop
|
||||
while (sa.getTargets().getNumTargeted() < abTgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
if (list.isEmpty()) {
|
||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !c.isType("Legendary") || c.getController().isOpponentOf(aiPlayer);
|
||||
}
|
||||
});
|
||||
Card choice;
|
||||
if (!CardLists.filter(list, Presets.CREATURES).isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||
} else {
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||
}
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
list.remove(choice);
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
} else {
|
||||
// if no targeting, it should always be ok
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
//TODO: add logic here
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
// Select a card to attach to
|
||||
return ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.Card;
|
||||
import forge.card.ability.AbilityUtils;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.cardfactory.CardFactoryUtil;
|
||||
import forge.card.cost.Cost;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.card.spellability.Target;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ai.ComputerUtilCost;
|
||||
import forge.game.ai.ComputerUtilMana;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class CounterAi extends SpellAbilityAi {
|
||||
@@ -19,7 +19,7 @@ public class CounterAi extends SpellAbilityAi {
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
boolean toReturn = true;
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getSourceCard();
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false;
|
||||
@@ -35,22 +35,22 @@ public class CounterAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
final Target tgt = sa.getTarget();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
|
||||
final SpellAbility topSA = game.getStack().peekAbility();
|
||||
if (!CardFactoryUtil.isCounterableBy(topSA.getSourceCard(), sa) || topSA.getActivatingPlayer() == ai) {
|
||||
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai) {
|
||||
// might as well check for player's friendliness
|
||||
return false;
|
||||
}
|
||||
if (sa.hasParam("AITgts") && (topSA.getSourceCard() == null
|
||||
|| !topSA.getSourceCard().isValid(sa.getParam("AITgts"), sa.getActivatingPlayer(), source))) {
|
||||
if (sa.hasParam("AITgts") && (topSA.getHostCard() == null
|
||||
|| !topSA.getHostCard().isValid(sa.getParam("AITgts"), sa.getActivatingPlayer(), source))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tgt.resetTargets();
|
||||
sa.resetTargets();
|
||||
if (sa.canTargetSpellAbility(topSA)) {
|
||||
tgt.addTarget(topSA);
|
||||
sa.getTargets().add(topSA);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -108,27 +108,27 @@ public class CounterAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
final Target tgt = sa.getTarget();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
final Game game = ai.getGame();
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final SpellAbility topSA = game.getStack().peekAbility();
|
||||
if (!CardFactoryUtil.isCounterableBy(topSA.getSourceCard(), sa) || topSA.getActivatingPlayer() == ai) {
|
||||
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tgt.resetTargets();
|
||||
sa.resetTargets();
|
||||
if (sa.canTargetSpellAbility(topSA)) {
|
||||
tgt.addTarget(topSA);
|
||||
sa.getTargets().add(topSA);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
|
||||
|
||||
final Card source = sa.getSourceCard();
|
||||
final Card source = sa.getHostCard();
|
||||
if (unlessCost != null) {
|
||||
// Is this Usable Mana Sources? Or Total Available Mana?
|
||||
final int usableManaSources = ComputerUtilMana.getAvailableMana(ai.getOpponent(), true).size();
|
||||
@@ -15,18 +15,17 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.card.ability.ai;
|
||||
|
||||
import java.util.List;
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.Card;
|
||||
import forge.CardLists;
|
||||
import forge.CounterType;
|
||||
import forge.game.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -50,7 +49,7 @@ public abstract class CountersAi {
|
||||
* a {@link java.lang.String} object.
|
||||
* @param amount
|
||||
* a int.
|
||||
* @return a {@link forge.Card} object.
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
public static Card chooseCursedTarget(final List<Card> list, final String type, final int amount) {
|
||||
Card choice;
|
||||
@@ -83,7 +82,7 @@ public abstract class CountersAi {
|
||||
* a {@link forge.CardList} object.
|
||||
* @param type
|
||||
* a {@link java.lang.String} object.
|
||||
* @return a {@link forge.Card} object.
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
public static Card chooseBoonTarget(final List<Card> list, final String type) {
|
||||
Card choice;
|
||||
@@ -1,21 +1,21 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import forge.Card;
|
||||
import forge.CardLists;
|
||||
import forge.CounterType;
|
||||
import forge.card.ability.AbilityUtils;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.card.spellability.Target;
|
||||
import forge.game.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class CountersMoveAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
@@ -28,7 +28,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
// TODO handle proper calculation of X values based on Cost
|
||||
int amount = 0;
|
||||
if (!sa.getParam("CounterNum").equals("All")) {
|
||||
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
}
|
||||
// don't use it if no counters to add
|
||||
if (amount <= 0) {
|
||||
@@ -47,13 +47,13 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card host = sa.getSourceCard();
|
||||
final Target abTgt = sa.getTarget();
|
||||
final Card host = sa.getHostCard();
|
||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
int amount = 0;
|
||||
if (!sa.getParam("CounterNum").equals("All")) {
|
||||
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
}
|
||||
boolean chance = false;
|
||||
boolean preferred = true;
|
||||
@@ -124,7 +124,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
|
||||
// TODO - I think choice can be null here. Is that ok for
|
||||
// addTarget()?
|
||||
abTgt.addTarget(choice);
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
|
||||
return chance;
|
||||
@@ -0,0 +1,68 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CountersProliferateAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
boolean chance = true;
|
||||
|
||||
List<Card> cperms = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
for (final CounterType c1 : CounterType.values()) {
|
||||
if (crd.getCounters(c1) != 0 && !ComputerUtil.isNegativeCounter(c1, crd)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
List<Card> hperms = CardLists.filter(ai.getOpponent().getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
for (final CounterType c1 : CounterType.values()) {
|
||||
if (crd.getCounters(c1) != 0 && ComputerUtil.isNegativeCounter(c1, crd)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (cperms.isEmpty() && hperms.isEmpty() && ai.getOpponent().getPoisonCounters() == 0) {
|
||||
return false;
|
||||
}
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
boolean chance = true;
|
||||
|
||||
// TODO Make sure Human has poison counters or there are some counters
|
||||
// we want to proliferate
|
||||
return chance;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
}
|
||||
401
forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
Normal file
401
forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
Normal file
@@ -0,0 +1,401 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class CountersPutAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, final SpellAbility sa) {
|
||||
// AI needs to be expanded, since this function can be pretty complex
|
||||
// based on
|
||||
// what the expected targets could be
|
||||
final Random r = MyRandom.getRandom();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
List<Card> list;
|
||||
Card choice = null;
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||
|
||||
final Player player = sa.isCurse() ? ai.getOpponent() : ai;
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("Never".equals(sa.getParam("AILogic"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for these costs
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("Monstrosity") && source.isMonstrous()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.hasParam("LevelUp")) {
|
||||
// creatures enchanted by curse auras have low priority
|
||||
if (source.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
for (Card aura : source.getEnchantedBy()) {
|
||||
if (aura.getController().isOpponentOf(ai)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
int maxLevel = Integer.parseInt(sa.getParam("MaxLevel"));
|
||||
return source.getCounters(CounterType.LEVEL) < maxLevel;
|
||||
}
|
||||
|
||||
// TODO handle proper calculation of X values based on Cost
|
||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(amount));
|
||||
}
|
||||
|
||||
// don't use it if no counters to add
|
||||
if (amount <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Targeting
|
||||
if (abTgt != null) {
|
||||
sa.resetTargets();
|
||||
// target loop
|
||||
|
||||
list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.canBeTargetedBy(sa) && c.canReceiveCounters(CounterType.valueOf(type));
|
||||
}
|
||||
});
|
||||
|
||||
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
|
||||
|
||||
if (list.size() < abTgt.getMinTargets(source, sa)) {
|
||||
return false;
|
||||
}
|
||||
while (sa.getTargets().getNumTargeted() < abTgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
if (list.isEmpty()) {
|
||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.isCurse()) {
|
||||
choice = CountersAi.chooseCursedTarget(list, type, amount);
|
||||
} else {
|
||||
choice = CountersAi.chooseBoonTarget(list, type);
|
||||
}
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
list.remove(choice);
|
||||
sa.getTargets().add(choice);
|
||||
|
||||
if (divided) {
|
||||
abTgt.addDividedAllocation(choice, amount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
// Don't activate Curse abilities on my cards and non-curse abilites
|
||||
// on my opponents
|
||||
if (cards.isEmpty() || !cards.get(0).getController().equals(player)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int currCounters = cards.get(0).getCounters(CounterType.valueOf(type));
|
||||
// each non +1/+1 counter on the card is a 10% chance of not
|
||||
// activating this ability.
|
||||
|
||||
if (!(type.equals("P1P1") || type.equals("M1M1") || type.equals("ICE")) && (r.nextFloat() < (.1 * currCounters))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't use non P1P1/M1M1 counters before main 2 if possible
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& !sa.hasParam("ActivationPhases")
|
||||
&& !(type.equals("P1P1") || type.equals("M1M1"))
|
||||
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} // putCanPlayAI
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(final SpellAbility sa, Player ai) {
|
||||
boolean chance = true;
|
||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
Card choice = null;
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
|
||||
final Player player = sa.isCurse() ? ai.getOpponent() : ai;
|
||||
|
||||
if (abTgt != null) {
|
||||
List<Card> list =
|
||||
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), abTgt.getValidTgts(), source.getController(), source);
|
||||
|
||||
if (list.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
// target loop
|
||||
while (sa.getTargets().getNumTargeted() < abTgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return sa.canTarget(c);
|
||||
}
|
||||
});
|
||||
if (list.size() == 0) {
|
||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.isCurse()) {
|
||||
choice = CountersAi.chooseCursedTarget(list, type, amount);
|
||||
} else {
|
||||
choice = CountersAi.chooseBoonTarget(list, type);
|
||||
}
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
list.remove(choice);
|
||||
sa.getTargets().add(choice);
|
||||
if (divided) {
|
||||
abTgt.addDividedAllocation(choice, amount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chance;
|
||||
} // putPlayDrawbackAI
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
// boolean chance = true;
|
||||
boolean preferred = true;
|
||||
List<Card> list;
|
||||
boolean isCurse = sa.isCurse();
|
||||
final Player player = isCurse ? ai.getOpponent() : ai;
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
|
||||
if (abTgt == null) {
|
||||
// No target. So must be defined
|
||||
list = new ArrayList<Card>(AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa));
|
||||
|
||||
if (!mandatory) {
|
||||
// TODO - If Trigger isn't mandatory, when wouldn't we want to
|
||||
// put a counter?
|
||||
// things like Powder Keg, which are way too complex for the AI
|
||||
}
|
||||
} else {
|
||||
list = CardLists.getTargetableCards(player.getCardsIn(ZoneType.Battlefield), sa);
|
||||
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
|
||||
|
||||
if (list.isEmpty() && mandatory) {
|
||||
// If there isn't any prefered cards to target, gotta choose
|
||||
// non-preferred ones
|
||||
list = player.getOpponent().getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
|
||||
preferred = false;
|
||||
}
|
||||
// Not mandatory, or the the list was regenerated and is still
|
||||
// empty,
|
||||
// so return false since there are no targets
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Card choice = null;
|
||||
|
||||
// Choose targets here:
|
||||
if (isCurse) {
|
||||
if (preferred) {
|
||||
choice = CountersAi.chooseCursedTarget(list, type, amount);
|
||||
}
|
||||
|
||||
else {
|
||||
if (type.equals("M1M1")) {
|
||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
} else {
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (preferred) {
|
||||
choice = CountersAi.chooseBoonTarget(list, type);
|
||||
}
|
||||
|
||||
else {
|
||||
if (type.equals("P1P1")) {
|
||||
choice = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
} else {
|
||||
choice = Aggregates.random(list);
|
||||
}
|
||||
}
|
||||
if (choice != null && divided) {
|
||||
abTgt.addDividedAllocation(choice, amount);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - I think choice can be null here. Is that ok for
|
||||
// addTarget()?
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
final Card source = sa.getHostCard();
|
||||
if (mode == PlayerActionConfirmMode.Tribute) {
|
||||
// add counter if that opponent has a giant creature
|
||||
final List<Card> creats = player.getCreaturesInPlay();
|
||||
final int tributeAmount = source.getKeywordMagnitude("Tribute");
|
||||
final boolean isHaste = source.hasKeyword("Haste");
|
||||
List<Card> threatening = CardLists.filter(creats, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card c) {
|
||||
return CombatUtil.canBlock(source, c, !isHaste)
|
||||
&& (c.getNetDefense() > source.getNetAttack() + tributeAmount || c.hasKeyword("DeathTouch"));
|
||||
}
|
||||
});
|
||||
if (!threatening.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (source.hasSVar("TributeAILogic")) {
|
||||
final String logic = source.getSVar("TributeAILogic");
|
||||
if (logic.equals("Always")) {
|
||||
return true;
|
||||
} else if (logic.equals("Never")) {
|
||||
return false;
|
||||
} else if (logic.equals("CanBlockThisTurn")) {
|
||||
// pump haste
|
||||
List<Card> canBlock = CardLists.filter(creats, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card c) {
|
||||
return CombatUtil.canBlock(source, c) && (c.getNetDefense() > source.getNetAttack() || c.hasKeyword("DeathTouch"));
|
||||
}
|
||||
});
|
||||
if (!canBlock.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if (logic.equals("DontControlCreatures")) {
|
||||
return !creats.isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
return MyRandom.getRandom().nextBoolean();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(Player, SpellAbility, Collection<Player>)
|
||||
*/
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Collection<Player> options) {
|
||||
// logic?
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +1,26 @@
|
||||
package forge.card.ability.ai;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.Card;
|
||||
import forge.CardLists;
|
||||
import forge.card.ability.AbilityUtils;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.cost.Cost;
|
||||
import forge.card.spellability.AbilitySub;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.card.spellability.Target;
|
||||
import forge.game.ai.ComputerUtilCost;
|
||||
import forge.game.ai.ComputerUtilMana;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class CountersPutAllAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
@@ -29,14 +29,14 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
// the expected targets could be
|
||||
final Random r = MyRandom.getRandom();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getSourceCard();
|
||||
final Card source = sa.getHostCard();
|
||||
List<Card> hList;
|
||||
List<Card> cList;
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
final String valid = sa.getParam("ValidCards");
|
||||
final boolean curse = sa.isCurse();
|
||||
final Target tgt = sa.getTarget();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
hList = CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
||||
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
|
||||
@@ -58,7 +58,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
|
||||
if (tgt != null) {
|
||||
Player pl = curse ? ai.getOpponent() : ai;
|
||||
tgt.addTarget(pl);
|
||||
sa.getTargets().add(pl);
|
||||
|
||||
hList = CardLists.filterControlledBy(hList, pl);
|
||||
cList = CardLists.filterControlledBy(cList, pl);
|
||||
@@ -72,7 +72,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(amount));
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
@@ -133,4 +133,11 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return player.getCreaturesInPlay().size() >= player.getOpponent().getCreaturesInPlay().size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2011 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactory_PutOrRemoveCountersAi class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id$
|
||||
*/
|
||||
public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
return doTriggerAINoCost(ai, sa, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
// if Defined, don't worry about targeting
|
||||
|
||||
List<ZoneType> zones = ZoneType.listValueOf(sa.getParamOrDefault("TgtZones", "Battlefield"));
|
||||
List<Card> validCards = CardLists.getValidCards(ai.getGame().getCardsIn(zones),
|
||||
tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard());
|
||||
|
||||
if (validCards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Card> cWithCounters = CardLists.filter(validCards, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
return crd.hasCounters();
|
||||
}
|
||||
});
|
||||
|
||||
if (cWithCounters.isEmpty()) {
|
||||
if (mandatory) {
|
||||
cWithCounters = validCards;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
Card targetCard = null;
|
||||
if (cWithCounters.isEmpty() && ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().getNumTargeted() == 0))) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
int random = MyRandom.getRandom().nextInt(cWithCounters.size());
|
||||
targetCard = cWithCounters.get(random);
|
||||
|
||||
sa.getTargets().add(targetCard);
|
||||
cWithCounters.remove(targetCard);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.Card;
|
||||
import forge.CounterType;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.cost.Cost;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.card.spellability.Target;
|
||||
import forge.game.ai.ComputerUtil;
|
||||
import forge.game.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
public class CountersRemoveAi extends SpellAbilityAi {
|
||||
|
||||
@@ -19,8 +19,8 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
// based on what
|
||||
// the expected targets could be
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
Target abTgt = sa.getTarget();
|
||||
final Card source = sa.getSourceCard();
|
||||
TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
// List<Card> list;
|
||||
// Card choice = null;
|
||||
|
||||
@@ -59,7 +59,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// TODO handle proper calculation of X values based on Cost
|
||||
// final int amount = calculateAmount(sa.getSourceCard(), amountStr, sa);
|
||||
// final int amount = calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
@@ -77,7 +77,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (!type.matches("Any")) {
|
||||
final int currCounters = sa.getSourceCard().getCounters(CounterType.valueOf(type));
|
||||
final int currCounters = sa.getHostCard().getCounters(CounterType.valueOf(type));
|
||||
if (currCounters < 1) {
|
||||
return false;
|
||||
}
|
||||
@@ -1,19 +1,18 @@
|
||||
package forge.card.ability.ai;
|
||||
|
||||
import java.util.List;
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.Card;
|
||||
import forge.CardPredicates;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ai.ComputerUtilCombat;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
|
||||
int restDamage = d;
|
||||
@@ -22,15 +21,19 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
if (!sa.canTarget(enemy)) {
|
||||
return false;
|
||||
}
|
||||
if (sa.getTargets() != null && sa.getTargets().getTargets().contains(enemy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// burn Planeswalkers
|
||||
if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!noPrevention) {
|
||||
restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, sa.getSourceCard(), false);
|
||||
restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, sa.getHostCard(), false);
|
||||
} else {
|
||||
restDamage = enemy.staticReplaceDamage(restDamage, sa.getSourceCard(), false);
|
||||
restDamage = enemy.staticReplaceDamage(restDamage, sa.getHostCard(), false);
|
||||
}
|
||||
|
||||
if (restDamage == 0) {
|
||||
@@ -1,26 +1,21 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.Card;
|
||||
import forge.CardLists;
|
||||
import forge.card.ability.AbilityUtils;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.cost.Cost;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.card.spellability.Target;
|
||||
import forge.game.ai.ComputerUtilCard;
|
||||
import forge.game.ai.ComputerUtilCombat;
|
||||
import forge.game.ai.ComputerUtilCost;
|
||||
import forge.game.ai.ComputerUtilMana;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class DamageAllAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
@@ -29,12 +24,12 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
// based on what the expected targets could be
|
||||
final Random r = MyRandom.getRandom();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getSourceCard();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
String validP = "";
|
||||
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), damage, sa);
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
|
||||
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
@@ -51,10 +46,10 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
final List<Card> humanList = this.getKillableCreatures(sa, opp, dmg);
|
||||
List<Card> computerList = this.getKillableCreatures(sa, ai, dmg);
|
||||
|
||||
final Target tgt = sa.getTarget();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null && sa.canTarget(opp)) {
|
||||
tgt.resetTargets();
|
||||
sa.getTarget().addTarget(opp);
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
computerList = new ArrayList<Card>();
|
||||
}
|
||||
|
||||
@@ -104,11 +99,11 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
final Card source = sa.getSourceCard();
|
||||
final Card source = sa.getHostCard();
|
||||
String validP = "";
|
||||
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), damage, sa);
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
@@ -124,11 +119,11 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
Player enemy = ai.getOpponent();
|
||||
final List<Card> humanList = this.getKillableCreatures(sa, enemy, dmg);
|
||||
List<Card> computerList = this.getKillableCreatures(sa, ai, dmg);
|
||||
final Target tgt = sa.getTarget();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
if (tgt != null && sa.canTarget(enemy)) {
|
||||
tgt.resetTargets();
|
||||
sa.getTarget().addTarget(enemy);
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(enemy);
|
||||
computerList.clear();
|
||||
}
|
||||
// Don't get yourself killed
|
||||
@@ -156,9 +151,9 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
* </p>
|
||||
*
|
||||
* @param af
|
||||
* a {@link forge.card.ability.AbilityFactory} object.
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
* @param sa
|
||||
* a {@link forge.card.spellability.SpellAbility} object.
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param player
|
||||
* a {@link forge.game.player.Player} object.
|
||||
* @param dmg
|
||||
@@ -166,7 +161,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
* @return a {@link forge.CardList} object.
|
||||
*/
|
||||
private List<Card> getKillableCreatures(final SpellAbility sa, final Player player, final int dmg) {
|
||||
final Card source = sa.getSourceCard();
|
||||
final Card source = sa.getHostCard();
|
||||
String validC = sa.hasParam("ValidCards") ? sa.getParam("ValidCards") : "";
|
||||
|
||||
// TODO: X may be something different than X paid
|
||||
@@ -188,11 +183,11 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card source = sa.getSourceCard();
|
||||
final Card source = sa.getHostCard();
|
||||
String validP = "";
|
||||
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getSourceCard(), damage, sa);
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
@@ -208,11 +203,11 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
Player enemy = ai.getOpponent();
|
||||
final List<Card> humanList = this.getKillableCreatures(sa, enemy, dmg);
|
||||
List<Card> computerList = this.getKillableCreatures(sa, ai, dmg);
|
||||
final Target tgt = sa.getTarget();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
if (tgt != null && sa.canTarget(enemy)) {
|
||||
tgt.resetTargets();
|
||||
sa.getTarget().addTarget(enemy);
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(enemy);
|
||||
computerList.clear();
|
||||
}
|
||||
|
||||
516
forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
Normal file
516
forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
Normal file
@@ -0,0 +1,516 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetChoices;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class DamageDealAi extends DamageAiBase {
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
}
|
||||
if (!this.damageTargetAI(ai, sa, dmg)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
}
|
||||
String logic = sa.getParam("AILogic");
|
||||
|
||||
if ("DiscardLands".equals(logic)) {
|
||||
dmg = 2;
|
||||
} else if ("WildHunt".equals(logic)) {
|
||||
// This dummy ability will just deal 0 damage, but holds the logic for the AI for Master of Wild Hunt
|
||||
List<Card> wolves = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), "Creature.Wolf+untapped+YouCtrl+Other", ai, source);
|
||||
dmg = Aggregates.sum(wolves, CardPredicates.Accessors.fnGetNetAttack);
|
||||
}
|
||||
|
||||
if (dmg <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// temporarily disabled until better AI
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("DiscardLands".equals(sa.getParam("AILogic")) && !ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.isAbility()) {
|
||||
final Random r = MyRandom.getRandom(); // prevent run-away
|
||||
// activations
|
||||
if (r.nextFloat() > Math.pow(.9, sa.getActivationsThisTurn())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.damageTargetAI(ai, sa, dmg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (damage.equals("X") && source.getSVar(damage).equals("Count$xPaid")) {
|
||||
// If I can kill my target by paying less mana, do it
|
||||
if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.hasParam("DividedAsYouChoose")) {
|
||||
int actualPay = 0;
|
||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||
for (final Card c : sa.getTargets().getTargetCards()) {
|
||||
final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||
if ((adjDamage > actualPay) && (adjDamage <= dmg)) {
|
||||
actualPay = adjDamage;
|
||||
}
|
||||
}
|
||||
source.setSVar("PayX", Integer.toString(actualPay));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* dealDamageChooseTgtC.
|
||||
* </p>
|
||||
*
|
||||
* @param d
|
||||
* a int.
|
||||
* @param noPrevention
|
||||
* a boolean.
|
||||
* @param pl
|
||||
* a {@link forge.game.player.Player} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
private Card dealDamageChooseTgtC(final Player ai, final SpellAbility sa, final int d, final boolean noPrevention,
|
||||
final Player pl, final boolean mandatory) {
|
||||
|
||||
// wait until stack is empty (prevents duplicate kills)
|
||||
if (!sa.isTrigger() && !ai.getGame().getStack().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
List<Card> hPlay = CardLists.getValidCards(pl.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, source);
|
||||
|
||||
final List<GameObject> objects = Lists.newArrayList(sa.getTargets().getTargets());
|
||||
if (sa.hasParam("TargetUnique")) {
|
||||
objects.addAll(sa.getUniqueTargets());
|
||||
}
|
||||
for (final Object o : objects) {
|
||||
if (o instanceof Card) {
|
||||
final Card c = (Card) o;
|
||||
if (hPlay.contains(c)) {
|
||||
hPlay.remove(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
hPlay = CardLists.getTargetableCards(hPlay, sa);
|
||||
|
||||
final List<Card> killables = CardLists.filter(hPlay, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d) && !ComputerUtil.canRegenerate(ai, c)
|
||||
&& !(c.getSVar("SacMe").length() > 0);
|
||||
}
|
||||
});
|
||||
|
||||
Card targetCard;
|
||||
if (pl.isOpponentOf(ai) && !killables.isEmpty()) {
|
||||
targetCard = ComputerUtilCard.getBestCreatureAI(killables);
|
||||
|
||||
return targetCard;
|
||||
}
|
||||
|
||||
if (!mandatory) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!hPlay.isEmpty()) {
|
||||
if (pl.isOpponentOf(ai)) {
|
||||
targetCard = ComputerUtilCard.getBestCreatureAI(hPlay);
|
||||
} else {
|
||||
targetCard = ComputerUtilCard.getWorstCreatureAI(hPlay);
|
||||
}
|
||||
|
||||
return targetCard;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* damageTargetAI.
|
||||
* </p>
|
||||
*
|
||||
* @param saMe
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param dmg
|
||||
* a int.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean damageTargetAI(final Player ai, final SpellAbility saMe, final int dmg) {
|
||||
final TargetRestrictions tgt = saMe.getTargetRestrictions();
|
||||
|
||||
if (tgt == null) {
|
||||
return this.damageChooseNontargeted(ai, saMe, dmg);
|
||||
}
|
||||
|
||||
if (tgt.isRandomTarget()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.damageChoosingTargets(ai, saMe, tgt, dmg, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* damageChoosingTargets.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param tgt
|
||||
* a {@link forge.game.spellability.TargetRestrictions} object.
|
||||
* @param dmg
|
||||
* a int.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean damageChoosingTargets(final Player ai, final SpellAbility sa, final TargetRestrictions tgt, int dmg,
|
||||
final boolean isTrigger, final boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||
final Game game = source.getGame();
|
||||
final PhaseHandler phase = game.getPhaseHandler();
|
||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||
|
||||
// target loop
|
||||
sa.resetTargets();
|
||||
TargetChoices tcs = sa.getTargets();
|
||||
Player enemy = ai.getOpponent();
|
||||
|
||||
if (tgt.getMaxTargets(source, sa) <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (tcs.getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
|
||||
if (tgt.canTgtCreatureAndPlayer()) {
|
||||
|
||||
if (this.shouldTgtP(ai, sa, dmg, noPrevention)) {
|
||||
tcs.add(enemy);
|
||||
if (divided) {
|
||||
tgt.addDividedAllocation(enemy, dmg);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, enemy, false);
|
||||
if (c != null) {
|
||||
tcs.add(c);
|
||||
if (divided) {
|
||||
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||
if (assignedDamage <= dmg) {
|
||||
tgt.addDividedAllocation(c, assignedDamage);
|
||||
}
|
||||
dmg = dmg - assignedDamage;
|
||||
if (dmg <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// When giving priority to targeting Creatures for mandatory
|
||||
// triggers
|
||||
// feel free to add the Human after we run out of good targets
|
||||
|
||||
// TODO: add check here if card is about to die from something
|
||||
// on the stack
|
||||
// or from taking combat damage
|
||||
boolean freePing = isTrigger || sa.getPayCosts() == null || sa.getTargets().getNumTargeted() > 0;
|
||||
|
||||
if (phase.is(PhaseType.END_OF_TURN) && sa.isAbility()) {
|
||||
if (phase.getNextTurn().equals(ai))
|
||||
freePing = true;
|
||||
}
|
||||
|
||||
if (phase.is(PhaseType.MAIN2) && sa.isAbility()) {
|
||||
if (sa.getRestrictions().getPlaneswalker() || source.hasKeyword("At the beginning of the end step, exile CARDNAME.")
|
||||
|| source.hasKeyword("At the beginning of the end step, sacrifice CARDNAME."))
|
||||
freePing = true;
|
||||
}
|
||||
|
||||
if (freePing && sa.canTarget(enemy)) {
|
||||
tcs.add(enemy);
|
||||
if (divided) {
|
||||
tgt.addDividedAllocation(enemy, dmg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (tgt.canTgtCreature()) {
|
||||
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, enemy, mandatory);
|
||||
if (c != null) {
|
||||
tcs.add(c);
|
||||
if (divided) {
|
||||
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||
if (assignedDamage <= dmg) {
|
||||
tgt.addDividedAllocation(c, assignedDamage);
|
||||
} else {
|
||||
tgt.addDividedAllocation(c, dmg);
|
||||
}
|
||||
dmg = dmg - assignedDamage;
|
||||
if (dmg <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Improve Damage, we shouldn't just target the player just
|
||||
// because we can
|
||||
else if (sa.canTarget(enemy)) {
|
||||
if ((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
||||
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|
||||
|| sa.getPayCosts() == null || isTrigger
|
||||
|| this.shouldTgtP(ai, sa, dmg, noPrevention)) {
|
||||
sa.getTargets().add(enemy);
|
||||
if (divided) {
|
||||
tgt.addDividedAllocation(enemy, dmg);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// fell through all the choices, no targets left?
|
||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa) || sa.getTargets().getNumTargeted() == 0) {
|
||||
if (!mandatory) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// If the trigger is mandatory, gotta choose my own stuff now
|
||||
return this.damageChooseRequiredTargets(ai, sa, tgt, dmg, mandatory);
|
||||
}
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* damageChooseNontargeted.
|
||||
* </p>
|
||||
* @param ai
|
||||
*
|
||||
* @param saMe
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param dmg
|
||||
* a int.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean damageChooseNontargeted(Player ai, final SpellAbility saMe, final int dmg) {
|
||||
// TODO: Improve circumstances where the Defined Damage is unwanted
|
||||
final List<GameObject> objects = AbilityUtils.getDefinedObjects(saMe.getHostCard(), saMe.getParam("Defined"), saMe);
|
||||
boolean urgent = false; // can it wait?
|
||||
boolean positive = false;
|
||||
|
||||
for (final Object o : objects) {
|
||||
if (o instanceof Card) {
|
||||
Card c = (Card) o;
|
||||
final int restDamage = ComputerUtilCombat.predictDamageTo(c, dmg, saMe.getHostCard(), false);
|
||||
if (!c.hasKeyword("Indestructible") && ComputerUtilCombat.getDamageToKill(c) <= restDamage) {
|
||||
if (c.getController().equals(ai)) {
|
||||
return false;
|
||||
} else {
|
||||
urgent = true;
|
||||
}
|
||||
}
|
||||
if (c.getController().isOpponentOf(ai) ^ c.getName().equals("Stuffy Doll")) {
|
||||
positive = true;
|
||||
}
|
||||
} else if (o instanceof Player) {
|
||||
final Player p = (Player) o;
|
||||
final int restDamage = ComputerUtilCombat.predictDamageTo(p, dmg, saMe.getHostCard(), false);
|
||||
if (!p.isOpponentOf(ai) && p.canLoseLife() && restDamage + 3 >= p.getLife() && restDamage > 0) {
|
||||
// from this spell will kill me
|
||||
return false;
|
||||
}
|
||||
if (p.isOpponentOf(ai) && p.canLoseLife()) {
|
||||
positive = true;
|
||||
if (p.getLife() + 3 <= restDamage) {
|
||||
urgent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!positive && !(saMe instanceof AbilitySub)) {
|
||||
return false;
|
||||
}
|
||||
if (!urgent && !SpellAbilityAi.playReusable(ai, saMe)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* damageChooseRequiredTargets.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param tgt
|
||||
* a {@link forge.game.spellability.TargetRestrictions} object.
|
||||
* @param dmg
|
||||
* a int.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean damageChooseRequiredTargets(final Player ai, final SpellAbility sa, final TargetRestrictions tgt, final int dmg,
|
||||
final boolean mandatory) {
|
||||
// this is for Triggered targets that are mandatory
|
||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||
final boolean divided = sa.hasParam("DividedAsYouChoose");
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
// TODO: Consider targeting the planeswalker
|
||||
if (tgt.canTgtCreature()) {
|
||||
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, ai, mandatory);
|
||||
if (c != null) {
|
||||
sa.getTargets().add(c);
|
||||
if (divided) {
|
||||
tgt.addDividedAllocation(c, dmg);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.canTarget(ai)) {
|
||||
if (sa.getTargets().add(ai)) {
|
||||
if (divided) {
|
||||
tgt.addDividedAllocation(ai, dmg);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// if we get here then there isn't enough targets, this is the only
|
||||
// time we can return false
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
int dmg = AbilityUtils.calculateAmount(source, damage, sa);
|
||||
|
||||
// Remove all damage
|
||||
if (sa.hasParam("Remove")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(dmg));
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt == null) {
|
||||
// If it's not mandatory check a few things
|
||||
if (!mandatory && !this.damageChooseNontargeted(ai, sa, dmg)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!this.damageChoosingTargets(ai, sa, tgt, dmg, true, mandatory) && !mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (damage.equals("X") && source.getSVar(damage).equals("Count$xPaid") && !sa.hasParam("DividedAsYouChoose")) {
|
||||
// If I can kill my target by paying less mana, do it
|
||||
int actualPay = 0;
|
||||
final boolean noPrevention = sa.hasParam("NoPrevention");
|
||||
|
||||
//target is a player
|
||||
if (!sa.getTargets().isTargetingAnyCard()) {
|
||||
actualPay = dmg;
|
||||
}
|
||||
for (final Card c : sa.getTargets().getTargetCards()) {
|
||||
final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||
if (adjDamage > actualPay) {
|
||||
actualPay = adjDamage;
|
||||
}
|
||||
}
|
||||
|
||||
source.setSVar("PayX", Integer.toString(actualPay));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
43
forge-ai/src/main/java/forge/ai/ability/DamageEachAi.java
Normal file
43
forge-ai/src/main/java/forge/ai/ability/DamageEachAi.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
public class DamageEachAi extends DamageAiBase {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
if (tgt != null && sa.canTarget(ai.getOpponent())) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
}
|
||||
|
||||
final String damage = sa.getParam("NumDmg");
|
||||
final int iDmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
return this.shouldTgtP(ai, sa, iDmg, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
// check AI life before playing this drawback?
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
}
|
||||
224
forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java
Normal file
224
forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java
Normal file
@@ -0,0 +1,224 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetChoices;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DamagePreventAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Card hostCard = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
boolean chance = false;
|
||||
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
// temporarily disabled until better AI
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt == null) {
|
||||
// As far as I can tell these Defined Cards will only have one of
|
||||
// them
|
||||
final List<GameObject> objects = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
|
||||
// react to threats on the stack
|
||||
if (!game.getStack().isEmpty()) {
|
||||
final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
|
||||
for (final Object o : objects) {
|
||||
if (threatenedObjects.contains(o)) {
|
||||
chance = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PhaseHandler handler = game.getPhaseHandler();
|
||||
if (handler.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
boolean flag = false;
|
||||
for (final Object o : objects) {
|
||||
if (o instanceof Card) {
|
||||
flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, (Card) o, combat);
|
||||
} else if (o instanceof Player) {
|
||||
// Don't need to worry about Combat Damage during AI's turn
|
||||
final Player p = (Player) o;
|
||||
if (!handler.isPlayerTurn(p)) {
|
||||
flag |= (p == ai && ((ComputerUtilCombat.wouldLoseLife(ai, combat) && sa
|
||||
.isAbility()) || ComputerUtilCombat.lifeInDanger(ai, combat)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chance = flag;
|
||||
} else { // if nothing on the stack, and it's not declare
|
||||
// blockers. no need to prevent
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} // non-targeted
|
||||
|
||||
// react to threats on the stack
|
||||
else if (!game.getStack().isEmpty()) {
|
||||
sa.resetTargets();
|
||||
final TargetChoices tcs = sa.getTargets();
|
||||
// check stack for something on the stack will kill anything i control
|
||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
|
||||
|
||||
if (objects.contains(ai)) {
|
||||
tcs.add(ai);
|
||||
chance = true;
|
||||
}
|
||||
final List<Card> threatenedTargets = new ArrayList<Card>();
|
||||
// filter AIs battlefield by what I can target
|
||||
List<Card> targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, hostCard);
|
||||
targetables = CardLists.getTargetableCards(targetables, sa);
|
||||
|
||||
for (final Card c : targetables) {
|
||||
if (objects.contains(c)) {
|
||||
threatenedTargets.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (!threatenedTargets.isEmpty()) {
|
||||
// Choose "best" of the remaining to save
|
||||
tcs.add(ComputerUtilCard.getBestCreatureAI(threatenedTargets));
|
||||
chance = true;
|
||||
}
|
||||
|
||||
} // Protect combatants
|
||||
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
sa.resetTargets();
|
||||
final TargetChoices tcs = sa.getTargets();
|
||||
if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, combat)
|
||||
&& (ComputerUtilCombat.lifeInDanger(ai, combat) || sa.isAbility() || sa.isTrigger())
|
||||
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
|
||||
tcs.add(ai);
|
||||
chance = true;
|
||||
} else {
|
||||
// filter AIs battlefield by what I can target
|
||||
List<Card> targetables = ai.getCardsIn(ZoneType.Battlefield);
|
||||
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, hostCard);
|
||||
targetables = CardLists.getTargetableCards(targetables, sa);
|
||||
|
||||
if (targetables.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final List<Card> combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES);
|
||||
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
||||
|
||||
for (final Card c : combatants) {
|
||||
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && tcs.getNumTargeted() < tgt.getMaxTargets(hostCard, sa)) {
|
||||
tcs.add(c);
|
||||
chance = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().getTargets().isEmpty()) {
|
||||
tgt.addDividedAllocation(sa.getTargets().getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||
}
|
||||
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
boolean chance = false;
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt == null) {
|
||||
// If there's no target on the trigger, just say yes.
|
||||
chance = true;
|
||||
} else {
|
||||
chance = preventDamageMandatoryTarget(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
return chance;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* preventDamageMandatoryTarget.
|
||||
* </p>
|
||||
*
|
||||
* @param af
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
sa.resetTargets();
|
||||
// filter AIs battlefield by what I can target
|
||||
final Game game = ai.getGame();
|
||||
List<Card> targetables = game.getCardsIn(ZoneType.Battlefield);
|
||||
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getHostCard());
|
||||
final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai);
|
||||
Card target = null;
|
||||
|
||||
if (targetables.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mandatory && compTargetables.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!compTargetables.isEmpty()) {
|
||||
final List<Card> combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
|
||||
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
||||
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
Combat combat = game.getCombat();
|
||||
for (final Card c : combatants) {
|
||||
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
|
||||
target = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (target == null) {
|
||||
target = combatants.get(0);
|
||||
}
|
||||
} else {
|
||||
target = ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true);
|
||||
}
|
||||
sa.getTargets().add(target);
|
||||
if (sa.hasParam("DividedAsYouChoose")) {
|
||||
tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.Card;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.cost.Cost;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.game.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class DamagePreventAllAi extends SpellAbilityAi {
|
||||
|
||||
@@ -16,7 +16,7 @@ public class DamagePreventAllAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Card hostCard = sa.getSourceCard();
|
||||
final Card hostCard = sa.getHostCard();
|
||||
boolean chance = false;
|
||||
|
||||
final Cost cost = sa.getPayCosts();
|
||||
298
forge-ai/src/main/java/forge/ai/ability/DebuffAi.java
Normal file
298
forge-ai/src/main/java/forge/ai/ability/DebuffAi.java
Normal file
@@ -0,0 +1,298 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityRestriction;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class DebuffAi extends SpellAbilityAi {
|
||||
// *************************************************************************
|
||||
// ***************************** Debuff ************************************
|
||||
// *************************************************************************
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||
// if there is no target and host card isn't in play, don't activate
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
if ((sa.getTargetRestrictions() == null) && !source.isInPlay()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
// temporarily disabled until AI is improved
|
||||
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 40, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final SpellAbilityRestriction restrict = sa.getRestrictions();
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
|
||||
// Phase Restrictions
|
||||
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
|| !game.getStack().isEmpty()) {
|
||||
// Instant-speed pumps should not be cast outside of combat when the
|
||||
// stack is empty
|
||||
if (!SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final int activations = restrict.getNumberTurnActivations();
|
||||
final int sacActivations = restrict.getActivationNumberSacrifice();
|
||||
// don't risk sacrificing a creature just to pump it
|
||||
if ((sacActivations != -1) && (activations >= (sacActivations - 1))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) {
|
||||
List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
|
||||
|
||||
|
||||
final Combat combat = game.getCombat();
|
||||
return Iterables.any(cards, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
|
||||
if (c.getController().equals(sa.getActivatingPlayer()) || combat == null)
|
||||
return false;
|
||||
|
||||
if (!combat.isBlocking(c) && !combat.isAttacking(c)) {
|
||||
return false;
|
||||
}
|
||||
// don't add duplicate negative keywords
|
||||
return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & ")));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
|
||||
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be
|
||||
// here?
|
||||
} else {
|
||||
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // debuffDrawbackAI()
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* debuffTgtAI.
|
||||
* </p>
|
||||
*
|
||||
* @param af
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param kws
|
||||
* a {@link java.util.ArrayList} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean debuffTgtAI(final Player ai, final SpellAbility sa, final List<String> kws, final boolean mandatory) {
|
||||
// this would be for evasive things like Flying, Unblockable, etc
|
||||
if (!mandatory && ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
sa.resetTargets();
|
||||
List<Card> list = getCurseCreatures(ai, sa, kws == null ? Lists.<String>newArrayList() : kws);
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard());
|
||||
|
||||
// several uses here:
|
||||
// 1. make human creatures lose evasion when they are attacking
|
||||
// 2. make human creatures lose Flying/Horsemanship/Shadow/etc. when
|
||||
// Comp is attacking
|
||||
// 3. remove Indestructible keyword so it can be destroyed?
|
||||
// 3a. remove Persist?
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return mandatory && debuffMandatoryTarget(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
Card t = null;
|
||||
// boolean goodt = false;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
|
||||
if (mandatory) {
|
||||
return debuffMandatoryTarget(ai, sa, mandatory);
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
t = ComputerUtilCard.getBestCreatureAI(list);
|
||||
sa.getTargets().add(t);
|
||||
list.remove(t);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // pumpTgtAI()
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getCurseCreatures.
|
||||
* </p>
|
||||
*
|
||||
* @param af
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param kws
|
||||
* a {@link java.util.ArrayList} object.
|
||||
* @return a {@link forge.CardList} object.
|
||||
*/
|
||||
private List<Card> getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
|
||||
final Player opp = ai.getOpponent();
|
||||
List<Card> list = opp.getCreaturesInPlay();
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
|
||||
if (!list.isEmpty()) {
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.hasAnyKeyword(kws); // don't add duplicate negative
|
||||
// keywords
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
} // getCurseCreatures()
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* debuffMandatoryTarget.
|
||||
* </p>
|
||||
*
|
||||
* @param af
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @return a boolean.
|
||||
*/
|
||||
private boolean debuffMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
List<Card> list = ai.getGame().getCardsIn(ZoneType.Battlefield);
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard());
|
||||
|
||||
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove anything that's already been targeted
|
||||
for (final Card c : sa.getTargets().getTargetCards()) {
|
||||
list.remove(c);
|
||||
}
|
||||
|
||||
final List<Card> pref = CardLists.filterControlledBy(list, ai.getOpponent());
|
||||
final List<Card> forced = CardLists.filterControlledBy(list, ai);
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
|
||||
if (pref.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Card c;
|
||||
if (CardLists.getNotType(pref, "Creature").size() == 0) {
|
||||
c = ComputerUtilCard.getBestCreatureAI(pref);
|
||||
} else {
|
||||
c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
|
||||
}
|
||||
|
||||
pref.remove(c);
|
||||
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (forced.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO - if forced targeting, just pick something without the given
|
||||
// keyword
|
||||
Card c;
|
||||
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
||||
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
||||
} else {
|
||||
c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
|
||||
}
|
||||
|
||||
forced.remove(c);
|
||||
|
||||
sa.getTargets().add(c);
|
||||
}
|
||||
|
||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} // pumpMandatoryTarget()
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final List<String> kws = sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList<String>();
|
||||
|
||||
if (sa.getTargetRestrictions() == null) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return debuffTgtAI(ai, sa, kws, mandatory);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +1,26 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.Card;
|
||||
import forge.CardLists;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.game.phase.CombatUtil;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class DebuffAllAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
String valid = "";
|
||||
final Random r = MyRandom.getRandom();
|
||||
// final Card source = sa.getSourceCard();
|
||||
final Card hostCard = sa.getSourceCard();
|
||||
// final Card source = sa.getHostCard();
|
||||
final Card hostCard = sa.getHostCard();
|
||||
final Player opp = ai.getOpponent();
|
||||
|
||||
final boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); // to
|
||||
@@ -0,0 +1,50 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiPlayDecision;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.SpellApiToAi;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class DelayedTriggerAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
final String svarName = sa.getParam("Execute");
|
||||
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
|
||||
trigsa.setActivatingPlayer(ai);
|
||||
|
||||
if (trigsa instanceof AbilitySub) {
|
||||
return SpellApiToAi.Converter.get(((AbilitySub) trigsa).getApi()).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
|
||||
} else {
|
||||
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final String svarName = sa.getParam("Execute");
|
||||
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
|
||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
trigsa.setActivatingPlayer(ai);
|
||||
|
||||
if (!sa.hasParam("OptionalDecider")) {
|
||||
return aic.doTrigger(trigsa, true);
|
||||
} else {
|
||||
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final String svarName = sa.getParam("Execute");
|
||||
final SpellAbility trigsa = AbilityFactory.getAbility(sa.getHostCard().getSVar(svarName), sa.getHostCard());
|
||||
trigsa.setActivatingPlayer(ai);
|
||||
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
|
||||
}
|
||||
|
||||
}
|
||||
266
forge-ai/src/main/java/forge/ai/ability/DestroyAi.java
Normal file
266
forge-ai/src/main/java/forge/ai/ability/DestroyAi.java
Normal file
@@ -0,0 +1,266 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostSacrifice;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class DestroyAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||
// AI needs to be expanded, since this function can be pretty complex
|
||||
// based on what the expected targets could be
|
||||
final Random r = MyRandom.getRandom();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final boolean noRegen = sa.hasParam("NoRegen");
|
||||
List<Card> list;
|
||||
|
||||
if (abCost != null) {
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
// Targeting
|
||||
if (abTgt != null) {
|
||||
sa.resetTargets();
|
||||
list = CardLists.getTargetableCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), sa);
|
||||
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source);
|
||||
if (sa.hasParam("AITgts")) {
|
||||
list = CardLists.getValidCards(list, sa.getParam("AITgts"), sa.getActivatingPlayer(), source);
|
||||
}
|
||||
list = CardLists.getNotKeyword(list, "Indestructible");
|
||||
if (!SpellAbilityAi.playReusable(ai, sa)) {
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
//Check for cards that can be sacrificed in response
|
||||
for (final SpellAbility ability : c.getAllSpellAbilities()) {
|
||||
if (ability.isAbility()) {
|
||||
final Cost cost = ability.getPayCosts();
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (!(part instanceof CostSacrifice)) {
|
||||
continue;
|
||||
}
|
||||
CostSacrifice sacCost = (CostSacrifice) part;
|
||||
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//Check for undying
|
||||
return (!c.hasKeyword("Undying") || c.getCounters(CounterType.P1P1) > 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If NoRegen is not set, filter out creatures that have a
|
||||
// regeneration shield
|
||||
if (!noRegen) {
|
||||
// TODO filter out things that might be tougher?
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return (c.getShield().isEmpty() && !ComputerUtil.canRegenerate(ai, c));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (list.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
// target loop
|
||||
while (sa.getTargets().getNumTargeted() < abTgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
if (list.size() == 0) {
|
||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Card choice = null;
|
||||
// If the targets are only of one type, take the best
|
||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||
} else if (CardLists.getNotType(list, "Land").isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestLandAI(list);
|
||||
} else {
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||
}
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
||||
|| (sa.getTargets().getNumTargeted() == 0)) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
// TODO is this good enough? for up to amounts?
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Don't destroy stolen permanents when the stealing aura can be destroyed
|
||||
if (choice.getOwner() == ai) {
|
||||
for (Card aura : choice.getEnchantedBy()) {
|
||||
SpellAbility sp = aura.getFirstSpellAbility();
|
||||
if (sp != null && "GainControl".equals(sp.getParam("AILogic"))
|
||||
&& aura.getController() != ai && sa.canTarget(aura)) {
|
||||
choice = aura;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
list.remove(choice);
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
} else {
|
||||
if (sa.hasParam("Defined")) {
|
||||
list = new ArrayList<Card>(AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa));
|
||||
if (list.isEmpty()
|
||||
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|
||||
|| CardLists.getNotKeyword(list, "Indestructible").isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final boolean noRegen = sa.hasParam("NoRegen");
|
||||
if (tgt != null) {
|
||||
List<Card> list;
|
||||
list = ai.getGame().getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source);
|
||||
|
||||
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
|
||||
List<Card> preferred = CardLists.getNotKeyword(list, "Indestructible");
|
||||
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
|
||||
|
||||
// If NoRegen is not set, filter out creatures that have a
|
||||
// regeneration shield
|
||||
if (!noRegen) {
|
||||
// TODO filter out things that could regenerate in response?
|
||||
// might be tougher?
|
||||
preferred = CardLists.filter(preferred, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.getShield().isEmpty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (final Card c : preferred) {
|
||||
list.remove(c);
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
if (preferred.isEmpty()) {
|
||||
if ((sa.getTargets().getNumTargeted() == 0)
|
||||
|| (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa))) {
|
||||
if (!mandatory) {
|
||||
sa.resetTargets();
|
||||
return false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
Card c;
|
||||
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
|
||||
c = ComputerUtilCard.getBestCreatureAI(preferred);
|
||||
} else if (CardLists.getNotType(preferred, "Land").isEmpty()) {
|
||||
c = ComputerUtilCard.getBestLandAI(preferred);
|
||||
} else {
|
||||
c = ComputerUtilCard.getMostExpensivePermanentAI(preferred, sa, false);
|
||||
}
|
||||
sa.getTargets().add(c);
|
||||
preferred.remove(c);
|
||||
}
|
||||
}
|
||||
|
||||
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
if (list.isEmpty()) {
|
||||
break;
|
||||
} else {
|
||||
Card c;
|
||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||
c = ComputerUtilCard.getWorstCreatureAI(list);
|
||||
} else {
|
||||
c = ComputerUtilCard.getCheapestPermanentAI(list, sa, false);
|
||||
}
|
||||
sa.getTargets().add(c);
|
||||
list.remove(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!mandatory) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
143
forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java
Normal file
143
forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java
Normal file
@@ -0,0 +1,143 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DestroyAllAi extends SpellAbilityAi {
|
||||
|
||||
private static final Predicate<Card> predicate = new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !(c.hasKeyword("Indestructible") || c.getSVar("SacMe").length() > 0);
|
||||
}
|
||||
};
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card source = sa.getHostCard();
|
||||
String valid = "";
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
if (sa.hasParam("ValidCards")) {
|
||||
valid = sa.getParam("ValidCards");
|
||||
}
|
||||
List<Card> humanlist = CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source);
|
||||
List<Card> computerlist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source);
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
computerlist.clear();
|
||||
}
|
||||
|
||||
humanlist = CardLists.filter(humanlist, predicate);
|
||||
computerlist = CardLists.filter(computerlist, predicate);
|
||||
if (humanlist.isEmpty() && !computerlist.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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) >= ComputerUtilCard.evaluateCreatureList(humanlist)
|
||||
&& !computerlist.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} // otherwise evaluate both lists by CMC and pass only if human
|
||||
// permanents are more valuable
|
||||
else if (ComputerUtilCard.evaluatePermanentList(computerlist) >= ComputerUtilCard.evaluatePermanentList(humanlist)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
//TODO: Check for bad outcome
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
||||
// AI needs to be expanded, since this function can be pretty complex
|
||||
// based on what the expected targets could be
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
String valid = "";
|
||||
|
||||
if (sa.hasParam("ValidCards")) {
|
||||
valid = sa.getParam("ValidCards");
|
||||
}
|
||||
|
||||
if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
valid = valid.replace("X", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
List<Card> humanlist = CardLists.getValidCards(ai.getOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source);
|
||||
List<Card> computerlist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source);
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
computerlist.clear();
|
||||
}
|
||||
|
||||
humanlist = CardLists.filter(humanlist, predicate);
|
||||
computerlist = CardLists.filter(computerlist, predicate);
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for some costs
|
||||
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if only creatures are affected evaluate both lists and pass only if
|
||||
// human creatures are more valuable
|
||||
if (CardLists.getNotType(humanlist, "Creature").isEmpty() && CardLists.getNotType(computerlist, "Creature").isEmpty()) {
|
||||
if (ComputerUtilCard.evaluateCreatureList(computerlist) + 200 >= ComputerUtilCard.evaluateCreatureList(humanlist)) {
|
||||
return false;
|
||||
}
|
||||
} // only lands involved
|
||||
else if (CardLists.getNotType(humanlist, "Land").isEmpty() && CardLists.getNotType(computerlist, "Land").isEmpty()) {
|
||||
if (ai.isCardInPlay("Crucible of Worlds") && !ai.getOpponent().isCardInPlay("Crucible of Worlds") && !humanlist.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
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 true;
|
||||
}
|
||||
|
||||
}
|
||||
102
forge-ai/src/main/java/forge/ai/ability/DigAi.java
Normal file
102
forge-ai/src/main/java/forge/ai/ability/DigAi.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
public class DigAi extends SpellAbilityAi {
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
|
||||
Player opp = ai.getOpponent();
|
||||
final Card host = sa.getHostCard();
|
||||
Player libraryOwner = ai;
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (!opp.canBeTargetedBy(sa)) {
|
||||
return false;
|
||||
} else {
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
libraryOwner = opp;
|
||||
}
|
||||
|
||||
// return false if nothing to dig into
|
||||
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't deck yourself
|
||||
if (sa.hasParam("DestinationZone2") && !"Library".equals(sa.getParam("DestinationZone2"))) {
|
||||
int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
|
||||
if (libraryOwner == ai && ai.getCardsIn(ZoneType.Library).size() <= numToDig + 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't use draw abilities before main 2 if possible
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||
&& !sa.hasParam("DestinationZone") && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Random r = MyRandom.getRandom();
|
||||
boolean randomReturn = r.nextFloat() <= Math.pow(0.9, sa.getActivationsThisTurn());
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ai.getOpponent();
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else if (mandatory && sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> valid, boolean isOptional, Player relatedPlayer) {
|
||||
Card chosen = ComputerUtilCard.getBestAI(valid);
|
||||
if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) {
|
||||
return ComputerUtilCard.getWorstAI(valid);
|
||||
}
|
||||
return chosen;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
// looks like perfect code for Delver of Secrets, but what about other cards?
|
||||
Card topc = player.getZone(ZoneType.Library).get(0);
|
||||
return topc.isInstant() || topc.isSorcery();
|
||||
}
|
||||
}
|
||||
117
forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java
Normal file
117
forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java
Normal file
@@ -0,0 +1,117 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class DigUntilAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
Card source = sa.getHostCard();
|
||||
double chance = .4; // 40 percent chance with instant speed stuff
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
chance = .667; // 66.7% chance for sorcery speed (since it will
|
||||
// never activate EOT)
|
||||
}
|
||||
final Random r = MyRandom.getRandom();
|
||||
final boolean randomReturn = r.nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||
|
||||
Player libraryOwner = ai;
|
||||
Player opp = ai.getOpponent();
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (!sa.canTarget(opp)) {
|
||||
return false;
|
||||
} else {
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
libraryOwner = opp;
|
||||
} else {
|
||||
if (sa.hasParam("Valid")) {
|
||||
final String valid = sa.getParam("Valid");
|
||||
if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid.split(","), source.getController(), source).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final String num = sa.getParam("Amount");
|
||||
if ((num != null) && num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
if (!(sa instanceof AbilitySub) || source.getSVar("PayX").equals("")) {
|
||||
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
if (numCards <= 0) {
|
||||
return false;
|
||||
}
|
||||
source.setSVar("PayX", Integer.toString(numCards));
|
||||
}
|
||||
}
|
||||
|
||||
// return false if nothing to dig into
|
||||
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (sa.isCurse()) {
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mandatory && sa.getTargets().isEmpty() && sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
} else {
|
||||
if (sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
if (sa.hasParam("AILogic")) {
|
||||
final String logic = sa.getParam("AILogic");
|
||||
if ("OathOfDruids".equals(logic)) {
|
||||
final List<Card> creaturesInLibrary =
|
||||
CardLists.filter(player.getCardsIn(ZoneType.Library), CardPredicates.Presets.CREATURES);
|
||||
final List<Card> creaturesInBattlefield =
|
||||
CardLists.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
||||
// if there are at least 3 creatures in library,
|
||||
// or none in play with one in library, oath
|
||||
return creaturesInLibrary.size() > 2
|
||||
|| (creaturesInBattlefield.size() == 0 && creaturesInLibrary.size() > 0);
|
||||
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,29 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import forge.Card;
|
||||
import forge.card.ability.AbilityUtils;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.cost.Cost;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.card.spellability.Target;
|
||||
import forge.game.ai.ComputerUtil;
|
||||
import forge.game.ai.ComputerUtilCost;
|
||||
import forge.game.ai.ComputerUtilMana;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class DiscardAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Target tgt = sa.getTarget();
|
||||
final Card source = sa.getSourceCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
|
||||
if (abCost != null) {
|
||||
@@ -54,7 +54,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
}
|
||||
} else {
|
||||
// TODO: Add appropriate restrictions
|
||||
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getSourceCard(),
|
||||
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(),
|
||||
sa.getParam("Defined"), sa);
|
||||
|
||||
if (players.size() == 1) {
|
||||
@@ -85,7 +85,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
}
|
||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
} else {
|
||||
if (AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa) < 1) {
|
||||
if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -113,14 +113,14 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
} // discardCanPlayAI()
|
||||
|
||||
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
|
||||
final Target tgt = sa.getTarget();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
Player opp = ai.getOpponent();
|
||||
if (opp.getCardsIn(ZoneType.Hand).isEmpty()) {
|
||||
if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) {
|
||||
return false;
|
||||
}
|
||||
if (tgt != null) {
|
||||
if (sa.canTarget(opp)) {
|
||||
tgt.addTarget(opp);
|
||||
sa.getTargets().add(opp);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -131,24 +131,24 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Target tgt = sa.getTarget();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
Player opp = ai.getOpponent();
|
||||
if (!discardTargetAI(ai, sa)) {
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
tgt.addTarget(opp);
|
||||
sa.getTargets().add(opp);
|
||||
} else if (mandatory && sa.canTarget(ai)) {
|
||||
tgt.addTarget(ai);
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ("X".equals(sa.getParam("RevealNumber")) && sa.getSourceCard().getSVar("X").equals("Count$xPaid")) {
|
||||
if ("X".equals(sa.getParam("RevealNumber")) && sa.getHostCard().getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getOpponent()
|
||||
.getCardsIn(ZoneType.Hand).size());
|
||||
sa.getSourceCard().setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
// Drawback AI improvements
|
||||
// if parent draws cards, make sure cards in hand + cards drawn > 0
|
||||
final Target tgt = sa.getTarget();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
return discardTargetAI(ai, sa);
|
||||
}
|
||||
91
forge-ai/src/main/java/forge/ai/ability/DrainManaAi.java
Normal file
91
forge-ai/src/main/java/forge/ai/ability/DrainManaAi.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class DrainManaAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
// AI cannot use this properly until he can use SAs during Humans turn
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Player opp = ai.getOpponent();
|
||||
final Random r = MyRandom.getRandom();
|
||||
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
if (tgt == null) {
|
||||
// assume we are looking to tap human's stuff
|
||||
// TODO - check for things with untap abilities, and don't tap
|
||||
// those.
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (!defined.contains(opp)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ai.getOpponent();
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (null == tgt) {
|
||||
if (mandatory) {
|
||||
return true;
|
||||
} else {
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (!defined.contains(opp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
// AI cannot use this properly until he can use SAs during Humans turn
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
boolean randomReturn = true;
|
||||
|
||||
if (tgt == null) {
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (defined.contains(ai)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
}
|
||||
@@ -16,30 +16,23 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import forge.Card;
|
||||
import forge.card.ability.AbilityUtils;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.cost.Cost;
|
||||
import forge.card.cost.CostDiscard;
|
||||
import forge.card.cost.CostPart;
|
||||
import forge.card.cost.PaymentDecision;
|
||||
import forge.card.spellability.AbilitySub;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.card.spellability.Target;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.ai.ComputerUtil;
|
||||
import forge.game.ai.ComputerUtilCost;
|
||||
import forge.game.ai.ComputerUtilMana;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostDiscard;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.PaymentDecision;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class DrawAi extends SpellAbilityAi {
|
||||
|
||||
@@ -53,8 +46,8 @@ public class DrawAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Target tgt = sa.getTarget();
|
||||
final Card source = sa.getSourceCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
@@ -69,10 +62,10 @@ public class DrawAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
AiCostDecision aiDecisions = new AiCostDecision(ai, sa);
|
||||
for (final CostPart part : abCost.getCostParts()) {
|
||||
if (part instanceof CostDiscard) {
|
||||
CostDiscard cd = (CostDiscard) part;
|
||||
PaymentDecision decision = cd.decideAIPayment(ai, sa, sa.getSourceCard());
|
||||
PaymentDecision decision = part.accept(aiDecisions);
|
||||
if ( null == decision )
|
||||
return false;
|
||||
for (Card discard : decision.cards) {
|
||||
@@ -95,12 +88,25 @@ public class DrawAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (tgt != null) {
|
||||
final List<Player> players = tgt.getTargetPlayers();
|
||||
if ((players.size() > 0) && players.get(0).isOpponentOf(ai)) {
|
||||
final Player player = sa.getTargets().getFirstTargetedPlayer();
|
||||
if (player != null && player.isOpponentOf(ai)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sa.getConditions() != null && !sa.getConditions().areMet(sa) && sa.getSubAbility() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't use draw abilities before main 2 if possible
|
||||
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& !sa.hasParam("ActivationPhases") && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
@@ -110,7 +116,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)
|
||||
&& ai.getCardsIn(ZoneType.Hand).size() > 1
|
||||
&& !ComputerUtil.ActivateForSacCost(sa, ai)) {
|
||||
&& !ComputerUtil.activateForCost(sa, ai)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -119,28 +125,12 @@ public class DrawAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
double chance = .4; // 40 percent chance of drawing with instant speed
|
||||
// stuff
|
||||
final Random r = MyRandom.getRandom();
|
||||
boolean randomReturn = r.nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
randomReturn = true;
|
||||
}
|
||||
if ((game.getPhaseHandler().is(PhaseType.END_OF_TURN)
|
||||
&& game.getPhaseHandler().getNextTurn().equals(ai))) {
|
||||
randomReturn = true;
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.playReusable(ai, sa)) {
|
||||
randomReturn = true;
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final Target tgt = sa.getTarget();
|
||||
final Card source = sa.getSourceCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final boolean drawback = (sa instanceof AbilitySub);
|
||||
final Game game = ai.getGame();
|
||||
Player opp = ai.getOpponent();
|
||||
@@ -157,7 +147,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
|
||||
int numCards = 1;
|
||||
if (sa.hasParam("NumCards")) {
|
||||
numCards = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa);
|
||||
numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
|
||||
}
|
||||
|
||||
boolean xPaid = false;
|
||||
@@ -175,12 +165,11 @@ public class DrawAi extends SpellAbilityAi {
|
||||
//if (n)
|
||||
|
||||
// TODO: if xPaid and one of the below reasons would fail, instead of
|
||||
// bailing
|
||||
// reduce toPay amount to acceptable level
|
||||
// bailing reduce toPay amount to acceptable level
|
||||
|
||||
if (tgt != null) {
|
||||
// ability is targeted
|
||||
tgt.resetTargets();
|
||||
sa.resetTargets();
|
||||
|
||||
final boolean canTgtHuman = sa.canTarget(opp);
|
||||
final boolean canTgtComp = sa.canTarget(ai);
|
||||
@@ -192,7 +181,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
|
||||
if (canTgtHuman && !opp.cantLose() && numCards >= humanLibrarySize) {
|
||||
// Deck the Human? DO IT!
|
||||
tgt.addTarget(opp);
|
||||
sa.getTargets().add(opp);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -228,9 +217,9 @@ public class DrawAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if ((!tgtHuman || !canTgtHuman) && canTgtComp) {
|
||||
tgt.addTarget(ai);
|
||||
sa.getTargets().add(ai);
|
||||
} else if (mandatory && canTgtHuman) {
|
||||
tgt.addTarget(opp);
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -275,7 +264,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumCards"), sa) : 1;
|
||||
int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) : 1;
|
||||
// AI shouldn't mill itself
|
||||
return numCards < player.getZone(ZoneType.Library).size();
|
||||
}
|
||||
163
forge-ai/src/main/java/forge/ai/ability/EffectAi.java
Normal file
163
forge-ai/src/main/java/forge/ai/ability/EffectAi.java
Normal file
@@ -0,0 +1,163 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class EffectAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
final Random r = MyRandom.getRandom();
|
||||
boolean randomReturn = r.nextFloat() <= .6667;
|
||||
final Player opp = ai.getOpponent();
|
||||
String logic = "";
|
||||
|
||||
if (sa.hasParam("AILogic")) {
|
||||
logic = sa.getParam("AILogic");
|
||||
final PhaseHandler phase = game.getPhaseHandler();
|
||||
if (logic.equals("BeginningOfOppTurn")) {
|
||||
if (phase.isPlayerTurn(ai) || phase.getPhase().isAfter(PhaseType.DRAW)) {
|
||||
return false;
|
||||
}
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("EndOfOppTurn")) {
|
||||
if (phase.isPlayerTurn(ai) || phase.getPhase().isBefore(PhaseType.END_OF_TURN)) {
|
||||
return false;
|
||||
}
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("Fog")) {
|
||||
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||
return false;
|
||||
}
|
||||
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
if (!game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
|
||||
return false;
|
||||
}
|
||||
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
|
||||
return false;
|
||||
}
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
List<Card> list = game.getCombat().getAttackers();
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard());
|
||||
list = CardLists.getTargetableCards(list, sa);
|
||||
Card target = ComputerUtilCard.getBestCreatureAI(list);
|
||||
if (target == null) {
|
||||
return false;
|
||||
}
|
||||
sa.getTargets().add(target);
|
||||
}
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("Always")) {
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("Main2")) {
|
||||
if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
return false;
|
||||
}
|
||||
randomReturn = true;
|
||||
} else if (logic.equals("Evasion")) {
|
||||
List<Card> comp = ai.getCreaturesInPlay();
|
||||
List<Card> human = opp.getCreaturesInPlay();
|
||||
|
||||
// only count creatures that can attack or block
|
||||
comp = CardLists.filter(comp, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return CombatUtil.canAttack(c, opp);
|
||||
}
|
||||
});
|
||||
human = CardLists.filter(human, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return CombatUtil.canBlock(c);
|
||||
}
|
||||
});
|
||||
if (comp.size() < 2 || human.size() < 1) {
|
||||
randomReturn = false;
|
||||
}
|
||||
} else if (logic.equals("RedirectSpellDamageFromPlayer")) {
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
boolean threatened = false;
|
||||
for (final SpellAbilityStackInstance stackSA : game.getStack()) {
|
||||
if (!stackSA.isSpell()) { continue; }
|
||||
if (stackSA.getSpellAbility().getApi() == ApiType.DealDamage) {
|
||||
final SpellAbility saTargeting = stackSA.getSpellAbility().getSATargetingPlayer();
|
||||
if (saTargeting != null && Iterables.contains(saTargeting.getTargets().getTargetPlayers(), ai)) {
|
||||
threatened = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
randomReturn = threatened;
|
||||
}
|
||||
} else { //no AILogic
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("False".equals(sa.getParam("Stackable"))) {
|
||||
String name = sa.getParam("Name");
|
||||
if (name == null) {
|
||||
name = sa.getHostCard().getName() + "'s Effect";
|
||||
}
|
||||
final List<Card> list = sa.getActivatingPlayer().getCardsIn(ZoneType.Command, name);
|
||||
if (!list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null && tgt.canTgtPlayer()) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent() || logic.equals("BeginningOfOppTurn")) {
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
} else {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
}
|
||||
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
final Player opp = aiPlayer.getOpponent();
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else if (mandatory && sa.canTarget(aiPlayer)) {
|
||||
sa.getTargets().add(aiPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
100
forge-ai/src/main/java/forge/ai/ability/EncodeAi.java
Normal file
100
forge-ai/src/main/java/forge/ai/ability/EncodeAi.java
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (C) 2011 Forge Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactoryBond class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
|
||||
*/
|
||||
public final class EncodeAi extends SpellAbilityAi {
|
||||
/**
|
||||
* <p>
|
||||
* bondCanPlayAI.
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param af
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
Card choice = null;
|
||||
// final String logic = sa.getParam("AILogic");
|
||||
// if (logic == null) {
|
||||
final List<Card> attackers = CardLists.filter(options, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return CombatUtil.canAttackNextTurn(c);
|
||||
}
|
||||
});
|
||||
final List<Card> unblockables = CardLists.filter(attackers, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !CombatUtil.canBeBlocked(c, ai.getOpponent());
|
||||
}
|
||||
});
|
||||
if (!unblockables.isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestAI(unblockables);
|
||||
} else if (!attackers.isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestAI(attackers);
|
||||
} else {
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
// }
|
||||
return choice;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
139
forge-ai/src/main/java/forge/ai/ability/FightAi.java
Normal file
139
forge-ai/src/main/java/forge/ai/ability/FightAi.java
Normal file
@@ -0,0 +1,139 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class FightAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
sa.resetTargets();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
List<Card> aiCreatures = ai.getCreaturesInPlay();
|
||||
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
|
||||
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
||||
|
||||
List<Card> humCreatures = ai.getOpponent().getCreaturesInPlay();
|
||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||
|
||||
final Random r = MyRandom.getRandom();
|
||||
if (r.nextFloat() > Math.pow(.6667, sa.getActivationsThisTurn())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//assumes the triggered card belongs to the ai
|
||||
if (sa.hasParam("Defined")) {
|
||||
Card fighter1 = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
|
||||
for (Card humanCreature : humCreatures) {
|
||||
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= fighter1.getNetAttack()
|
||||
&& humanCreature.getNetAttack() < ComputerUtilCombat.getDamageToKill(fighter1)) {
|
||||
// todo: check min/max targets; see if we picked the best matchup
|
||||
sa.getTargets().add(humanCreature);
|
||||
return true;
|
||||
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
|
||||
sa.getTargets().add(humanCreature);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("TargetsFromDifferentZone")) {
|
||||
if (humCreatures.isEmpty() && aiCreatures.isEmpty()) {
|
||||
for (Card humanCreature : humCreatures) {
|
||||
for (Card aiCreature : aiCreatures) {
|
||||
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetAttack()
|
||||
&& humanCreature.getNetAttack() < ComputerUtilCombat.getDamageToKill(aiCreature)) {
|
||||
// todo: check min/max targets; see if we picked the best matchup
|
||||
sa.getTargets().add(humanCreature);
|
||||
sa.getTargets().add(aiCreature);
|
||||
return true;
|
||||
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
|
||||
sa.getTargets().add(humanCreature);
|
||||
sa.getTargets().add(aiCreature);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for (Card creature1 : humCreatures) {
|
||||
for (Card creature2 : humCreatures) {
|
||||
if (creature1.equals(creature2)) {
|
||||
continue;
|
||||
}
|
||||
if (sa.hasParam("TargetsWithoutSameCreatureType")
|
||||
&& creature1.sharesCreatureTypeWith(creature2)) {
|
||||
continue;
|
||||
}
|
||||
if (ComputerUtilCombat.getDamageToKill(creature1) <= creature2.getNetAttack()
|
||||
&& creature1.getNetAttack() >= ComputerUtilCombat.getDamageToKill(creature2)) {
|
||||
// todo: check min/max targets; see if we picked the best matchup
|
||||
sa.getTargets().add(creature1);
|
||||
sa.getTargets().add(creature2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (canPlayAI(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
if (!mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//try to make a good trade or no trade
|
||||
final Card source = sa.getHostCard();
|
||||
List<Card> humCreatures = ai.getOpponent().getCreaturesInPlay();
|
||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||
if (humCreatures.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
//assumes the triggered card belongs to the ai
|
||||
if (sa.hasParam("Defined")) {
|
||||
Card aiCreature = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
|
||||
for (Card humanCreature : humCreatures) {
|
||||
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetAttack()
|
||||
&& ComputerUtilCard.evaluateCreature(humanCreature) > ComputerUtilCard.evaluateCreature(aiCreature)) {
|
||||
sa.getTargets().add(humanCreature);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (Card humanCreature : humCreatures) {
|
||||
if (ComputerUtilCombat.getDamageToKill(aiCreature) > humanCreature.getNetAttack()) {
|
||||
sa.getTargets().add(humanCreature);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
sa.getTargets().add(humCreatures.get(0));
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class FlipACoinAi extends SpellAbilityAi {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ai.ComputerUtilCombat;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class FogAi extends SpellAbilityAi {
|
||||
|
||||
49
forge-ai/src/main/java/forge/ai/ability/GameLossAi.java
Normal file
49
forge-ai/src/main/java/forge/ai/ability/GameLossAi.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
public class GameLossAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Player opp = ai.getOpponent();
|
||||
if (opp.cantLose()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only one SA Lose the Game card right now, which is Door to
|
||||
// Nothingness
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
|
||||
// In general, don't return true.
|
||||
// But this card wins the game, I can make an exception for that
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
// Phage the Untouchable
|
||||
// (Final Fortune would need to attach it's delayed trigger to a
|
||||
// specific turn, which can't be done yet)
|
||||
|
||||
if (!mandatory && ai.getOpponent().cantLose()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class GameWinAi extends SpellAbilityAi {
|
||||
/* (non-Javadoc)
|
||||
30
forge-ai/src/main/java/forge/ai/ability/HauntAi.java
Normal file
30
forge-ai/src/main/java/forge/ai/ability/HauntAi.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class HauntAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false; // should not get here
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> creats, boolean isOptional, Player targetedPlayer) {
|
||||
final List<Card> oppCreats = CardLists.filterControlledBy(creats, ai.getOpponents());
|
||||
return ComputerUtilCard.getWorstCreatureAI(oppCreats.isEmpty() ? creats : oppCreats);
|
||||
}
|
||||
|
||||
}
|
||||
41
forge-ai/src/main/java/forge/ai/ability/LegendaryRuleAi.java
Normal file
41
forge-ai/src/main/java/forge/ai/ability/LegendaryRuleAi.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class LegendaryRuleAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
return false; // should not get here
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Collection<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
// Choose a single legendary/planeswalker card to keep
|
||||
Card firstOption = Iterables.getFirst(options, null);
|
||||
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
|
||||
|
||||
if ( choosingFromPlanewalkers ) {
|
||||
// AI decision making - should AI compare counters?
|
||||
} else {
|
||||
// AI decision making - should AI compare damage and debuffs?
|
||||
}
|
||||
|
||||
return firstOption;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.card.spellability.Target;
|
||||
import forge.game.player.Player;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
public class LifeExchangeAi extends SpellAbilityAi {
|
||||
|
||||
/*
|
||||
@@ -37,12 +37,12 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
||||
* and one card that has a conditional (Psychic Transfer) that are
|
||||
* not currently handled
|
||||
*/
|
||||
final Target tgt = sa.getTarget();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
tgt.resetTargets();
|
||||
sa.resetTargets();
|
||||
if (opponent.canBeTargetedBy(sa)) {
|
||||
// never target self, that would be silly for exchange
|
||||
tgt.addTarget(opponent);
|
||||
sa.getTargets().add(opponent);
|
||||
if (!opponent.canLoseLife()) {
|
||||
return false;
|
||||
}
|
||||
161
forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java
Normal file
161
forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java
Normal file
@@ -0,0 +1,161 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
public class LifeGainAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = source.getGame();
|
||||
final int life = ai.getLife();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int lifeAmount = 0;
|
||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
lifeAmount = xPay;
|
||||
} else {
|
||||
lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
}
|
||||
|
||||
// don't use it if no life to gain
|
||||
if (!activateForCost && lifeAmount <= 0) {
|
||||
return false;
|
||||
}
|
||||
// don't play if the conditions aren't met, unless it would trigger a
|
||||
// beneficial sub-condition
|
||||
if (!activateForCost && !sa.getConditions().areMet(sa)) {
|
||||
final AbilitySub abSub = sa.getSubAbility();
|
||||
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
|
||||
if (!abSub.getConditions().areMet(abSub)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean lifeCritical = life <= 5;
|
||||
lifeCritical |= game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DAMAGE) && ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
|
||||
|
||||
if (abCost != null && !lifeCritical) {
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!activateForCost && !ai.canGainLife()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't use lifegain before main 2 if possible
|
||||
if (!lifeCritical && game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& !sa.hasParam("ActivationPhases") && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!lifeCritical && !activateForCost && (!game.getPhaseHandler().getNextTurn().equals(ai)
|
||||
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
|
||||
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* gainLifeDoTriggerAINoCost.
|
||||
* </p>
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @param af
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
||||
final boolean mandatory) {
|
||||
|
||||
// If the Target is gaining life, target self.
|
||||
// if the Target is modifying how much life is gained, this needs to be
|
||||
// handled better
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
} else if (mandatory && sa.canTarget(ai.getOpponent())) {
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
170
forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java
Normal file
170
forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java
Normal file
@@ -0,0 +1,170 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LifeLoseAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
|
||||
List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int amount = 0;
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
amount = xPay;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
|
||||
if (tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
|
||||
// TODO handle proper calculation of X values based on Cost and what
|
||||
// would be paid
|
||||
int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(amount));
|
||||
}
|
||||
|
||||
if (amount <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for these costs
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, amount, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Player opp = ai.getOpponent();
|
||||
|
||||
if (!opp.canLoseLife()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (amount >= opp.getLife()) {
|
||||
return true; // killing the human should be done asap
|
||||
}
|
||||
|
||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't use loselife before main 2 if possible
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& !sa.hasParam("ActivationPhases")
|
||||
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa)
|
||||
|| sa.hasParam("ActivationPhases")
|
||||
|| SpellAbilityAi.playReusable(ai, sa)
|
||||
|| ComputerUtil.activateForCost(sa, ai)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
|
||||
final boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
if (sa.canTarget(ai.getOpponent())) {
|
||||
sa.getTargets().add(ai.getOpponent());
|
||||
} else if (mandatory && sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
int amount = 0;
|
||||
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
amount = xPay;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
|
||||
List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
|
||||
if (!mandatory && tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
|
||||
// For cards like Foul Imp, ETB you lose life
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import forge.Card;
|
||||
import forge.CounterType;
|
||||
import forge.card.ability.AbilityUtils;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.card.spellability.Target;
|
||||
import forge.game.ai.ComputerUtilMana;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class LifeSetAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Random r = MyRandom.getRandom();
|
||||
// Ability_Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getSourceCard();
|
||||
final Card source = sa.getHostCard();
|
||||
final int myLife = ai.getLife();
|
||||
final Player opponent = ai.getOpponent();
|
||||
final int hlife = opponent.getLife();
|
||||
@@ -45,17 +45,17 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
amount = xPay;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
final boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
final Target tgt = sa.getTarget();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
tgt.resetTargets();
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
tgt.addTarget(opponent);
|
||||
sa.getTargets().add(opponent);
|
||||
// if we can only target the human, and the Human's life
|
||||
// would
|
||||
// go up, don't play it.
|
||||
@@ -67,11 +67,11 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
}
|
||||
} else {
|
||||
if ((amount > myLife) && (myLife <= 10)) {
|
||||
tgt.addTarget(ai);
|
||||
sa.getTargets().add(ai);
|
||||
} else if (hlife > amount) {
|
||||
tgt.addTarget(opponent);
|
||||
sa.getTargets().add(opponent);
|
||||
} else if (amount > myLife) {
|
||||
tgt.addTarget(ai);
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
final int myLife = ai.getLife();
|
||||
final Player opponent = ai.getOpponent();
|
||||
final int hlife = opponent.getLife();
|
||||
final Card source = sa.getSourceCard();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
|
||||
@@ -116,7 +116,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
source.setSVar("PayX", Integer.toString(xPay));
|
||||
amount = xPay;
|
||||
} else {
|
||||
amount = AbilityUtils.calculateAmount(sa.getSourceCard(), amountStr, sa);
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
}
|
||||
|
||||
if (source.getName().equals("Eternity Vessel")
|
||||
@@ -128,18 +128,18 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
// if the Target is modifying how much life is gained, this needs to
|
||||
// be
|
||||
// handled better
|
||||
final Target tgt = sa.getTarget();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
tgt.resetTargets();
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
tgt.addTarget(opponent);
|
||||
sa.getTargets().add(opponent);
|
||||
} else {
|
||||
if ((amount > myLife) && (myLife <= 10)) {
|
||||
tgt.addTarget(ai);
|
||||
sa.getTargets().add(ai);
|
||||
} else if (hlife > amount) {
|
||||
tgt.addTarget(opponent);
|
||||
sa.getTargets().add(opponent);
|
||||
} else if (amount > myLife) {
|
||||
tgt.addTarget(ai);
|
||||
sa.getTargets().add(ai);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
35
forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java
Normal file
35
forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class ManaEffectAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
if (ai.getGame().getPhaseHandler().is(PhaseType.MAIN2) && ComputerUtil.activateForCost(sa, ai)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sa
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @param mandatory
|
||||
* a boolean.
|
||||
* @param af
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
155
forge-ai/src/main/java/forge/ai/ability/MillAi.java
Normal file
155
forge-ai/src/main/java/forge/ai/ability/MillAi.java
Normal file
@@ -0,0 +1,155 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MillAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for these costs
|
||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!targetAI(ai, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't use draw abilities before main 2 if possible
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
|
||||
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z")) && source.getSVar("X").startsWith("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int cardsToDiscard =
|
||||
Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getOpponent().getCardsIn(ZoneType.Library).size());
|
||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
if (cardsToDiscard <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
Player opp = ai.getOpponent();
|
||||
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (!sa.canTarget(opp)) {
|
||||
if (mandatory && sa.canTarget(ai)) {
|
||||
sa.getTargets().add(ai);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
final int numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
|
||||
|
||||
final List<Card> pLibrary = opp.getCardsIn(ZoneType.Library);
|
||||
|
||||
if (pLibrary.isEmpty()) { // deck already empty, no need to mill
|
||||
if (!mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sa.getTargets().add(opp);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (numCards >= pLibrary.size()) {
|
||||
// Can Mill out Human's deck? Do it!
|
||||
sa.getTargets().add(opp);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Obscure case when you know what your top card is so you might?
|
||||
// want to mill yourself here
|
||||
// if (AI wants to mill self)
|
||||
// sa.getTargets().add(AllZone.getComputerPlayer());
|
||||
// else
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
return targetAI(aiPlayer, sa, true);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
if (!targetAI(aiPlayer, sa, mandatory)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, aiPlayer), aiPlayer.getOpponent()
|
||||
.getCardsIn(ZoneType.Library).size());
|
||||
source.setSVar("PayX", Integer.toString(cardsToDiscard));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package forge.card.ability.ai;
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class MustAttackAi extends SpellAbilityAi {
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
package forge.card.ability.ai;
|
||||
|
||||
import java.util.List;
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.Card;
|
||||
import forge.CardLists;
|
||||
import forge.CardPredicates;
|
||||
import forge.card.ability.AbilityUtils;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.card.spellability.Target;
|
||||
import forge.game.ai.ComputerUtilCard;
|
||||
import forge.game.ai.ComputerUtilCombat;
|
||||
import forge.game.phase.CombatUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.combat.CombatUtil;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MustBlockAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
@@ -34,8 +33,8 @@ public class MustBlockAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card source = sa.getSourceCard();
|
||||
final Target abTgt = sa.getTarget();
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
||||
|
||||
// only use on creatures that can attack
|
||||
if (!ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
@@ -44,7 +43,7 @@ public class MustBlockAi extends SpellAbilityAi {
|
||||
|
||||
Card attacker = null;
|
||||
if (sa.hasParam("DefinedAttacker")) {
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("DefinedAttacker"), sa);
|
||||
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedAttacker"), sa);
|
||||
if (cards.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
@@ -89,7 +88,7 @@ public class MustBlockAi extends SpellAbilityAi {
|
||||
if (blocker == null) {
|
||||
return false;
|
||||
}
|
||||
abTgt.addTarget(blocker);
|
||||
sa.getTargets().add(blocker);
|
||||
chance = true;
|
||||
} else {
|
||||
return false;
|
||||
39
forge-ai/src/main/java/forge/ai/ability/PeekAndRevealAi.java
Normal file
39
forge-ai/src/main/java/forge/ai/ability/PeekAndRevealAi.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.SpellApiToAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilityStatic;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class PeekAndRevealAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
if (sa instanceof AbilityStatic) {
|
||||
return false;
|
||||
}
|
||||
// So far this only appears on Triggers, but will expand
|
||||
// once things get converted from Dig + NoMove
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
AbilitySub subAb = sa.getSubAbility();
|
||||
return subAb != null && SpellApiToAi.Converter.get(subAb.getApi()).chkDrawbackWithSubs(player, subAb);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +1,19 @@
|
||||
package forge.card.ability.ai;
|
||||
|
||||
import java.util.List;
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.Card;
|
||||
import forge.CardPredicates;
|
||||
import forge.card.ability.SpellAbilityAi;
|
||||
import forge.card.spellability.SpellAbility;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ai.ComputerUtil;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AbilityFactory for Creature Spells.
|
||||
*
|
||||
@@ -46,7 +45,7 @@ public class PermanentCreatureAi extends SpellAbilityAi {
|
||||
for(Card c : cards) {
|
||||
ArrayList<StaticAbility> statics = c.getStaticAbilities();
|
||||
for(StaticAbility s : statics) {
|
||||
final Map<String, String> stabMap = s.getMapParams();
|
||||
final Map<String, String> stabMap = s.parseParams();
|
||||
|
||||
if (!stabMap.get("Mode").equals("Continuous")) {
|
||||
continue;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user