mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-14 01:38:13 +00:00
Compare commits
858 Commits
linkedAbil
...
forge-1.6.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9897ecb00 | ||
|
|
51362e8290 | ||
|
|
f9620ec816 | ||
|
|
385fcbe4e8 | ||
|
|
34e3bd2dc1 | ||
|
|
58c738e414 | ||
|
|
7e6496551c | ||
|
|
f8e3032671 | ||
|
|
103bffa681 | ||
|
|
e3ba05b612 | ||
|
|
f8ef3ef186 | ||
|
|
06c742ca2d | ||
|
|
29c13ccfc6 | ||
|
|
e583ebf60e | ||
|
|
d4aaa0751d | ||
|
|
76f95bd0a9 | ||
|
|
149f9f083c | ||
|
|
4c172ddb06 | ||
|
|
dfc53cd315 | ||
|
|
5f4e95e392 | ||
|
|
b6c2bf5630 | ||
|
|
be997820a3 | ||
|
|
e1ec1aa2fc | ||
|
|
568f026440 | ||
|
|
262dd1fb85 | ||
|
|
216e7286ea | ||
|
|
e43a1d1726 | ||
|
|
6bc2e035b3 | ||
|
|
906c66d1a4 | ||
|
|
07d0e20bc1 | ||
|
|
f4a8e1f8b1 | ||
|
|
447cfdbdb1 | ||
|
|
ec1dff4bab | ||
|
|
61747fcd75 | ||
|
|
186d71e452 | ||
|
|
9e6125d649 | ||
|
|
e0464d8b87 | ||
|
|
36ad56e6d7 | ||
|
|
9c4e44dc37 | ||
|
|
d733291b78 | ||
|
|
ae9b1745c6 | ||
|
|
7701ba4990 | ||
|
|
5e5ce90fa9 | ||
|
|
496ab01599 | ||
|
|
9a14ec945a | ||
|
|
07cacc406b | ||
|
|
39d74f5df2 | ||
|
|
edbce270ae | ||
|
|
63406fbca5 | ||
|
|
846e1e95bc | ||
|
|
e215eceafc | ||
|
|
36401b05ee | ||
|
|
a9715e4489 | ||
|
|
4c805f5e55 | ||
|
|
aee9a3a41d | ||
|
|
830e1d0469 | ||
|
|
4a1ac0e90c | ||
|
|
01a2dfe5fc | ||
|
|
628993f86d | ||
|
|
742395d1a1 | ||
|
|
a1dcb3617c | ||
|
|
a10e906ddb | ||
|
|
6be30ee2e4 | ||
|
|
a87ca115f1 | ||
|
|
dbd9e7f61d | ||
|
|
942998585c | ||
|
|
167a774c2a | ||
|
|
fea06524bd | ||
|
|
38b4fc2087 | ||
|
|
40b140a580 | ||
|
|
f10537c1e7 | ||
|
|
c3715fd4eb | ||
|
|
b2fcf62d1c | ||
|
|
edbc6553f4 | ||
|
|
d71213f8f8 | ||
|
|
966b9a0121 | ||
|
|
f237acdeb8 | ||
|
|
a95573fbb7 | ||
|
|
2a4311e24b | ||
|
|
3722fe98b1 | ||
|
|
cbf0341a2e | ||
|
|
a264d4a6a0 | ||
|
|
644b1c051a | ||
|
|
f5792e3755 | ||
|
|
757e4b2c92 | ||
|
|
8c5e549a66 | ||
|
|
f4db580f6a | ||
|
|
3ade673e74 | ||
|
|
6a723b7d13 | ||
|
|
6269a07632 | ||
|
|
e0a16981ff | ||
|
|
52e5ac04dc | ||
|
|
9925610cd8 | ||
|
|
4bad103bd1 | ||
|
|
62773ab38d | ||
|
|
f5747193d5 | ||
|
|
d189212786 | ||
|
|
6f08b0a558 | ||
|
|
a436c474c8 | ||
|
|
9656a494ad | ||
|
|
39747d2be3 | ||
|
|
f774c7d891 | ||
|
|
73a8711108 | ||
|
|
d7dff9b06c | ||
|
|
9e6accc441 | ||
|
|
af331e0f07 | ||
|
|
9c9cbf5bd3 | ||
|
|
8649ccdbf5 | ||
|
|
656643c772 | ||
|
|
467c836476 | ||
|
|
c4f0d4454f | ||
|
|
c7fdab7996 | ||
|
|
a35bc9c7b6 | ||
|
|
066361c9b7 | ||
|
|
ff5dc62445 | ||
|
|
49d475a418 | ||
|
|
ba6c09fd63 | ||
|
|
cbe816ac7a | ||
|
|
a6feacecc3 | ||
|
|
34c19db4c2 | ||
|
|
691465120a | ||
|
|
9bb6cf8535 | ||
|
|
2f923109d1 | ||
|
|
4abf6ed5f8 | ||
|
|
1a9a6042f1 | ||
|
|
15d5f9f560 | ||
|
|
faf42bc29f | ||
|
|
8bb4fdd110 | ||
|
|
1fcf6a7c24 | ||
|
|
cd441384fe | ||
|
|
cbff04ca46 | ||
|
|
ef90f497a6 | ||
|
|
b517a9005b | ||
|
|
d290566820 | ||
|
|
c138135376 | ||
|
|
6788ccd776 | ||
|
|
4898ce557c | ||
|
|
18733e046f | ||
|
|
e5773a5e24 | ||
|
|
910065e5a0 | ||
|
|
5918030022 | ||
|
|
7f5bc60208 | ||
|
|
9f17a67a63 | ||
|
|
875f6c78c0 | ||
|
|
a20e6e6a2c | ||
|
|
ebee360cda | ||
|
|
25e47f2836 | ||
|
|
ca9e80ddb7 | ||
|
|
c9a37c01bf | ||
|
|
b732e14012 | ||
|
|
8b9caeb2a6 | ||
|
|
21bfcf92c6 | ||
|
|
7c66787a32 | ||
|
|
6721fc5970 | ||
|
|
72ee0066ce | ||
|
|
2880a704e3 | ||
|
|
ca28ab16a0 | ||
|
|
4b710354a6 | ||
|
|
122629477b | ||
|
|
ff7e0f4870 | ||
|
|
404e2912cb | ||
|
|
1a1f36a759 | ||
|
|
e403720ab9 | ||
|
|
672b06fb4c | ||
|
|
b9ba160117 | ||
|
|
4506bb57d8 | ||
|
|
0ba7238360 | ||
|
|
7420f9a6b0 | ||
|
|
26a335e115 | ||
|
|
4f2f54207e | ||
|
|
77562edd43 | ||
|
|
ab622322b9 | ||
|
|
0c23cd661f | ||
|
|
3b0f6fc11a | ||
|
|
e94a0511b6 | ||
|
|
ebbe62b76c | ||
|
|
aad08bb6d9 | ||
|
|
48bc0737ec | ||
|
|
ee2ad22967 | ||
|
|
e5bda7b88a | ||
|
|
9aa93021f8 | ||
|
|
58470cef63 | ||
|
|
997b1462d3 | ||
|
|
578b35107c | ||
|
|
b373f1c941 | ||
|
|
4737bbb738 | ||
|
|
4567393e75 | ||
|
|
aa4fad07cb | ||
|
|
209488f024 | ||
|
|
193c58c7fb | ||
|
|
691aaf26ce | ||
|
|
f68e427a18 | ||
|
|
5081bf29fb | ||
|
|
031e64827e | ||
|
|
bd5a33edff | ||
|
|
2ca40f936b | ||
|
|
2b12d0e11a | ||
|
|
09f2ecba27 | ||
|
|
876e2749b9 | ||
|
|
2da6223526 | ||
|
|
4f2036270f | ||
|
|
b45191e6b5 | ||
|
|
bf80721f13 | ||
|
|
c3bcee0f8c | ||
|
|
923159bebb | ||
|
|
0cf08249ef | ||
|
|
1716da35d3 | ||
|
|
5ae456b3fb | ||
|
|
95c72210ab | ||
|
|
476a85c658 | ||
|
|
2a0b53f71b | ||
|
|
8caa76c0c6 | ||
|
|
14958c0203 | ||
|
|
a499054865 | ||
|
|
1ff3637443 | ||
|
|
bf44fd78b4 | ||
|
|
8c95e15b74 | ||
|
|
eebb1b1d37 | ||
|
|
c4561a4fb8 | ||
|
|
d6892f2e77 | ||
|
|
685e94233a | ||
|
|
8220857193 | ||
|
|
f9ebfddaab | ||
|
|
36c7270dbd | ||
|
|
9078724546 | ||
|
|
ace995973e | ||
|
|
8d87b2b6db | ||
|
|
ea57f316c5 | ||
|
|
27603a78e2 | ||
|
|
347842c7bc | ||
|
|
0eb04df4b6 | ||
|
|
6537696eef | ||
|
|
b9a01753ed | ||
|
|
853e8b64f0 | ||
|
|
4a4bd5588a | ||
|
|
3b43f78e0c | ||
|
|
b48e72b607 | ||
|
|
25bc52f9fe | ||
|
|
a6c29c9cfd | ||
|
|
b4120e0211 | ||
|
|
d490a93f53 | ||
|
|
a0e890fc50 | ||
|
|
f3d52fa36f | ||
|
|
f0e0ff1fa4 | ||
|
|
76cc5fa90e | ||
|
|
f7b6b99032 | ||
|
|
0cca0ba206 | ||
|
|
fe92c79a64 | ||
|
|
2bcdfcd06b | ||
|
|
90e99f3f66 | ||
|
|
dc796c14f2 | ||
|
|
9f297694c9 | ||
|
|
b057dcff06 | ||
|
|
960324efca | ||
|
|
159682561a | ||
|
|
94a5560db2 | ||
|
|
e6101ba1dd | ||
|
|
340878f6fb | ||
|
|
41b42cd610 | ||
|
|
f1a38d35bf | ||
|
|
969f8d2b17 | ||
|
|
44fd509809 | ||
|
|
5cdf780fb4 | ||
|
|
b17b7a5067 | ||
|
|
bcea70e32c | ||
|
|
78c274a6b5 | ||
|
|
613763b77f | ||
|
|
04d3d8ded2 | ||
|
|
ebd4870a59 | ||
|
|
07d9f9e105 | ||
|
|
4e83623e69 | ||
|
|
d8cc879a1e | ||
|
|
c9bd1644ba | ||
|
|
c7eba8400d | ||
|
|
d13908ed63 | ||
|
|
bcc15c5857 | ||
|
|
2b15ddae90 | ||
|
|
ea24f42b26 | ||
|
|
b2e1bb4ef5 | ||
|
|
24027156f6 | ||
|
|
13c3dbe8d0 | ||
|
|
3dda45713f | ||
|
|
ee03f26e04 | ||
|
|
d067e55c23 | ||
|
|
3501a546e5 | ||
|
|
b1de935c0b | ||
|
|
e5d73b6d3a | ||
|
|
d78108bc54 | ||
|
|
64063f7388 | ||
|
|
f3e4819a2b | ||
|
|
59aee823ad | ||
|
|
4f56632d42 | ||
|
|
cfacb6be7f | ||
|
|
143e353032 | ||
|
|
ef2d5c4d7c | ||
|
|
301cf14d7f | ||
|
|
28858205c6 | ||
|
|
40ac36a9b5 | ||
|
|
81f292a194 | ||
|
|
6afdda9a0f | ||
|
|
4c3fbb9810 | ||
|
|
89b212e9d0 | ||
|
|
ef3f3d44a9 | ||
|
|
51c5bbe010 | ||
|
|
786c89c185 | ||
|
|
a664ff6a52 | ||
|
|
8f06aaeb01 | ||
|
|
60d8cda45f | ||
|
|
8cf05af3c5 | ||
|
|
4cc092005a | ||
|
|
027ca95b7f | ||
|
|
33da935a5f | ||
|
|
95a404c5c5 | ||
|
|
b7b29510c6 | ||
|
|
2ea006961a | ||
|
|
d9574f9208 | ||
|
|
15fb11eca1 | ||
|
|
48b26050d7 | ||
|
|
f31b7f2686 | ||
|
|
66c6b05397 | ||
|
|
21dce32791 | ||
|
|
16f3138e8e | ||
|
|
f9c8af3296 | ||
|
|
a4186102b2 | ||
|
|
2a89690b4f | ||
|
|
9af710e48d | ||
|
|
a37ad6b923 | ||
|
|
73fd071935 | ||
|
|
70bfc13569 | ||
|
|
605dfb351f | ||
|
|
3e5b4fcf3f | ||
|
|
a15cfb716c | ||
|
|
45c7737624 | ||
|
|
0ff01b9fa6 | ||
|
|
b8aef0298c | ||
|
|
7206b42fec | ||
|
|
f476495958 | ||
|
|
fc4b9ffc61 | ||
|
|
ab2e293a29 | ||
|
|
0f61b17482 | ||
|
|
b47c9f0fec | ||
|
|
5a1d740243 | ||
|
|
24b2186e0d | ||
|
|
573e7dfb23 | ||
|
|
aaee31e1c6 | ||
|
|
f7556f90a8 | ||
|
|
14460a97bc | ||
|
|
9e1c06301f | ||
|
|
9bde0f210f | ||
|
|
defe53894e | ||
|
|
ac10d6f499 | ||
|
|
73f747b9ea | ||
|
|
2f75053478 | ||
|
|
8a86b32655 | ||
|
|
8996675c91 | ||
|
|
1260ae32df | ||
|
|
6dda7422ea | ||
|
|
19ab071643 | ||
|
|
84c156f389 | ||
|
|
9055556d65 | ||
|
|
488cabbde4 | ||
|
|
1493331238 | ||
|
|
50ff19b442 | ||
|
|
ea8ae179a8 | ||
|
|
e3293b8cc0 | ||
|
|
1754459459 | ||
|
|
c2f0b3cc14 | ||
|
|
892f21e8da | ||
|
|
4ccf2ff0df | ||
|
|
9e5573560c | ||
|
|
b40e971cdd | ||
|
|
2c844284a7 | ||
|
|
cc266d675b | ||
|
|
d307b78dc9 | ||
|
|
6380950831 | ||
|
|
e78f65558a | ||
|
|
d8cf7baba1 | ||
|
|
c28e2fbf78 | ||
|
|
4902ed4242 | ||
|
|
7d6fe5bc5b | ||
|
|
6fa6ce8680 | ||
|
|
64bb51fad4 | ||
|
|
b18c086c07 | ||
|
|
c55afd50de | ||
|
|
a3b6d34887 | ||
|
|
aa6830c454 | ||
|
|
73690dd461 | ||
|
|
53a7c0736e | ||
|
|
7789aeb3be | ||
|
|
f2fc19f352 | ||
|
|
e58e7825bc | ||
|
|
496dcd3838 | ||
|
|
2b9ce7df5d | ||
|
|
49ec28ac24 | ||
|
|
6455a3332c | ||
|
|
57eeb5e0bf | ||
|
|
284fbb9d53 | ||
|
|
b7a9bb9742 | ||
|
|
27af5756e1 | ||
|
|
0b3e9745f8 | ||
|
|
4a3eabd690 | ||
|
|
7a7dff9088 | ||
|
|
3dd2a2c076 | ||
|
|
bac850072a | ||
|
|
2e85504747 | ||
|
|
08c7a3d5b1 | ||
|
|
d95dd98fce | ||
|
|
771092a2b5 | ||
|
|
6c9ce36c17 | ||
|
|
a49015316e | ||
|
|
24970bff01 | ||
|
|
6c4367efa4 | ||
|
|
8b84ec9998 | ||
|
|
083ea2fc69 | ||
|
|
cdd7089534 | ||
|
|
b8334c8891 | ||
|
|
87bad87d54 | ||
|
|
99e00468bc | ||
|
|
e931169390 | ||
|
|
f2cefcff5d | ||
|
|
3fe10a24fa | ||
|
|
6b84716f53 | ||
|
|
e5f3488d5d | ||
|
|
5e9a76043a | ||
|
|
6b13169fd5 | ||
|
|
5427e51a04 | ||
|
|
f16825acdc | ||
|
|
f481d5d78c | ||
|
|
93e1a58a13 | ||
|
|
a028986abf | ||
|
|
66bcff8eea | ||
|
|
43ca318931 | ||
|
|
522ccc2f2d | ||
|
|
ebe1642bec | ||
|
|
e14ae15c70 | ||
|
|
75a974165d | ||
|
|
21fca37c7d | ||
|
|
e88c25ce7c | ||
|
|
8708b6300c | ||
|
|
b4a0f673f6 | ||
|
|
ace4ba4aea | ||
|
|
3c9cabfb7b | ||
|
|
c039f238d0 | ||
|
|
864d2caa12 | ||
|
|
dce75c9578 | ||
|
|
8828003333 | ||
|
|
5e8587d786 | ||
|
|
409d18aa06 | ||
|
|
a318749a78 | ||
|
|
8a05179a74 | ||
|
|
1f0c2d4a82 | ||
|
|
e60b6d00c7 | ||
|
|
eb0e97e0d7 | ||
|
|
e4e0edc217 | ||
|
|
01e8f8e2f2 | ||
|
|
ab2ea6554f | ||
|
|
9db791d875 | ||
|
|
721b284b1b | ||
|
|
e576656d7c | ||
|
|
efb713405f | ||
|
|
b5267d5a64 | ||
|
|
0e203c3327 | ||
|
|
1536c0e589 | ||
|
|
268b1ea823 | ||
|
|
774c4ff62d | ||
|
|
a16fbf15bc | ||
|
|
88adf6a99e | ||
|
|
f2fb22328a | ||
|
|
472b0b8329 | ||
|
|
2ecc88cada | ||
|
|
4972823b27 | ||
|
|
af51f1f5a5 | ||
|
|
64cbb0badd | ||
|
|
8af0a03ebb | ||
|
|
2b886345b2 | ||
|
|
4aca310370 | ||
|
|
9cd23e6639 | ||
|
|
1ceed9e52e | ||
|
|
d5d7c58bdd | ||
|
|
470e3d4a64 | ||
|
|
2cdb17f437 | ||
|
|
c3e6da0d35 | ||
|
|
e7a4dcf394 | ||
|
|
3698472ca7 | ||
|
|
64a1bff911 | ||
|
|
f2de64b1f2 | ||
|
|
3145fedee4 | ||
|
|
ac88f0b437 | ||
|
|
14608f34c4 | ||
|
|
908be0e653 | ||
|
|
e439e2a975 | ||
|
|
ae6f8f8f39 | ||
|
|
d9fd83bbd3 | ||
|
|
3f44105d93 | ||
|
|
88914f8fec | ||
|
|
317623fb34 | ||
|
|
a5e9e394da | ||
|
|
85c597672a | ||
|
|
1d055682ac | ||
|
|
ff026c59cc | ||
|
|
b6e42301bf | ||
|
|
166c61c7c2 | ||
|
|
105f2ce1f3 | ||
|
|
b476797001 | ||
|
|
16c3f4ed14 | ||
|
|
b8288c4e5a | ||
|
|
78345ade78 | ||
|
|
920bb96a57 | ||
|
|
11195e64d2 | ||
|
|
42f98bd8da | ||
|
|
b5608929fc | ||
|
|
7a9e9b0e19 | ||
|
|
84e4cf5bd2 | ||
|
|
e931da7ce0 | ||
|
|
fe0215673b | ||
|
|
c6e8f1d23a | ||
|
|
0fbdd526a6 | ||
|
|
10af16aa1d | ||
|
|
631ae3f9e5 | ||
|
|
509ba33d8e | ||
|
|
b0c545b039 | ||
|
|
9fd90a63c3 | ||
|
|
8251c08af0 | ||
|
|
47168e1de7 | ||
|
|
40028b77aa | ||
|
|
ebc12bed0f | ||
|
|
84bd068a06 | ||
|
|
50e068e047 | ||
|
|
6096207a85 | ||
|
|
9e95c7d2c0 | ||
|
|
abb1127fc9 | ||
|
|
ee13d15067 | ||
|
|
f01b695805 | ||
|
|
d96798edab | ||
|
|
60f569682d | ||
|
|
c9a02a52fb | ||
|
|
843c54acec | ||
|
|
92c3e98a5a | ||
|
|
2123a8a4db | ||
|
|
2c80d2aba3 | ||
|
|
ebd0eb3abe | ||
|
|
cf4707ead7 | ||
|
|
ee516ae5c6 | ||
|
|
2298502eb9 | ||
|
|
6ba4074362 | ||
|
|
2988499ff4 | ||
|
|
769ac2bf61 | ||
|
|
8b50f365a4 | ||
|
|
aadc9c1693 | ||
|
|
2bde63260a | ||
|
|
606926db24 | ||
|
|
ae0cdcd5c4 | ||
|
|
1b5b05b26c | ||
|
|
0f20fe4c1c | ||
|
|
0a66acfcbb | ||
|
|
0612c2a35b | ||
|
|
9d337f60c2 | ||
|
|
0ea643bffe | ||
|
|
8ca09b4d7e | ||
|
|
ca66cd9caa | ||
|
|
285fae8b72 | ||
|
|
7e19c32b48 | ||
|
|
5a90005622 | ||
|
|
468fc92606 | ||
|
|
d4fd068955 | ||
|
|
8aaaa9767b | ||
|
|
8e409e6e3c | ||
|
|
2028306b8e | ||
|
|
5944ffca50 | ||
|
|
17d41ef165 | ||
|
|
5d3519e533 | ||
|
|
adc336a25a | ||
|
|
387e2007d7 | ||
|
|
ce9f13bbe8 | ||
|
|
19c5fa069e | ||
|
|
2198947c53 | ||
|
|
1ba171d669 | ||
|
|
cb6f28c741 | ||
|
|
e28ce4b190 | ||
|
|
f1a9d542d5 | ||
|
|
f5f46ebc9b | ||
|
|
56413e2c4b | ||
|
|
a48769fc95 | ||
|
|
9a61ac45f4 | ||
|
|
047995c801 | ||
|
|
65f3275419 | ||
|
|
42ee46a9e7 | ||
|
|
568251df4a | ||
|
|
f0aa990aba | ||
|
|
7d9378f73f | ||
|
|
967122d829 | ||
|
|
b36bb7a39f | ||
|
|
9dcbc956d8 | ||
|
|
9ad1d67539 | ||
|
|
803f3d2d9f | ||
|
|
ae417d0200 | ||
|
|
3595e2bd26 | ||
|
|
b9fb174457 | ||
|
|
f76f466c39 | ||
|
|
c2bd4c1a4e | ||
|
|
7910b23a02 | ||
|
|
78ba4d9ef6 | ||
|
|
1a388f6e6b | ||
|
|
5e9734ec59 | ||
|
|
9646771d4c | ||
|
|
616324d364 | ||
|
|
b7293467aa | ||
|
|
50d015bbb5 | ||
|
|
fca6bf245e | ||
|
|
3bed91cba2 | ||
|
|
68a6fafa2b | ||
|
|
402835ee52 | ||
|
|
aa6e96284f | ||
|
|
a7f88d97bb | ||
|
|
07e9d46c87 | ||
|
|
eb88c12d6d | ||
|
|
1f1fdeabe4 | ||
|
|
d40439b2ec | ||
|
|
13dc89c3a2 | ||
|
|
1988d802f0 | ||
|
|
0a50d4bbde | ||
|
|
0fb56e9bc0 | ||
|
|
8d85df8b63 | ||
|
|
4c7af60445 | ||
|
|
ff46dc4520 | ||
|
|
b03c64b110 | ||
|
|
0b5b3ef756 | ||
|
|
640a1029f7 | ||
|
|
520306da7e | ||
|
|
b722aa7b4c | ||
|
|
dc24da9c2e | ||
|
|
b2164a239a | ||
|
|
7b4c73ed07 | ||
|
|
53655d8621 | ||
|
|
32d0f83fbf | ||
|
|
cfc10681ba | ||
|
|
477d62ec92 | ||
|
|
d08e925e9b | ||
|
|
f8b6c61b0d | ||
|
|
8a684f7d43 | ||
|
|
e4cb860d3a | ||
|
|
ce4d308610 | ||
|
|
20d0082822 | ||
|
|
3c222937fc | ||
|
|
843c03899b | ||
|
|
e0f1a47037 | ||
|
|
b41d76749c | ||
|
|
07bae8be9f | ||
|
|
266a905515 | ||
|
|
8f883168fa | ||
|
|
039f9da107 | ||
|
|
78cd586245 | ||
|
|
622752a96b | ||
|
|
3529e176ff | ||
|
|
99c10761d1 | ||
|
|
71464684ff | ||
|
|
23da591ff2 | ||
|
|
e0643ac0ec | ||
|
|
8bfbbe4008 | ||
|
|
9b714c1e12 | ||
|
|
34e36498e7 | ||
|
|
500548fd24 | ||
|
|
ba2e3be845 | ||
|
|
b2ca13d1f9 | ||
|
|
8e75620cca | ||
|
|
3ecee0c381 | ||
|
|
6e733af98f | ||
|
|
655928d93d | ||
|
|
4601e7a7ee | ||
|
|
da53a114e7 | ||
|
|
7554d6e9cf | ||
|
|
117a294539 | ||
|
|
fb1ca3461a | ||
|
|
1d58dafa9a | ||
|
|
dcf820e18d | ||
|
|
76cdd02f7e | ||
|
|
6bc82564c6 | ||
|
|
3004b0c6ac | ||
|
|
e972b6de47 | ||
|
|
1c214e733a | ||
|
|
7afb67a7da | ||
|
|
ef1781d261 | ||
|
|
ef6afbeba1 | ||
|
|
d4d8dc2aac | ||
|
|
e68b4050ed | ||
|
|
040a7e5d03 | ||
|
|
6ddb650e49 | ||
|
|
b2f05b51c9 | ||
|
|
1e67365ca0 | ||
|
|
423c55a3e3 | ||
|
|
81584e4dc9 | ||
|
|
b1f405332f | ||
|
|
b6e5c74c1a | ||
|
|
aad6ffc897 | ||
|
|
e04085b859 | ||
|
|
30dda88adb | ||
|
|
52f1dac9c9 | ||
|
|
0cca50c934 | ||
|
|
4877c24e8e | ||
|
|
a1dd01356c | ||
|
|
c550c4f6c4 | ||
|
|
62ff002ffb | ||
|
|
31fe583ef9 | ||
|
|
8aa798d57d | ||
|
|
4fe2b9f397 | ||
|
|
6d9b59074f | ||
|
|
74256f6b25 | ||
|
|
f54c52e142 | ||
|
|
12f6461906 | ||
|
|
05c8cf112a | ||
|
|
38027e2441 | ||
|
|
a3172d3275 | ||
|
|
35940cd409 | ||
|
|
2cd6d7a047 | ||
|
|
6429c94fa9 | ||
|
|
7d0f5a1944 | ||
|
|
d118be7ee4 | ||
|
|
b3aa288687 | ||
|
|
09aeae1558 | ||
|
|
f2d6c19d1d | ||
|
|
b290ca03fd | ||
|
|
72afbe8b85 | ||
|
|
6a7f46a102 | ||
|
|
c06c39ee6f | ||
|
|
35f003f759 | ||
|
|
92ea874fe4 | ||
|
|
b0aee6a5ca | ||
|
|
85fbede135 | ||
|
|
392c37cfdf | ||
|
|
72ed9896f7 | ||
|
|
0e98462096 | ||
|
|
8ec27032e6 | ||
|
|
481e701387 | ||
|
|
d6d3da2c60 | ||
|
|
b53ed7ade5 | ||
|
|
9275f53d26 | ||
|
|
b77dcaf3a8 | ||
|
|
d2e6281222 | ||
|
|
648b051a3b | ||
|
|
ce28f04bc6 | ||
|
|
1ce726b8ef | ||
|
|
7e61af3dca | ||
|
|
b6f15cf1d7 | ||
|
|
86d1f54830 | ||
|
|
f859bd951a | ||
|
|
8aa7f958de | ||
|
|
f21b202237 | ||
|
|
aa0fd6799c | ||
|
|
3d3e8d3a0b | ||
|
|
dc309f57bf | ||
|
|
c426a6b270 | ||
|
|
cac6321539 | ||
|
|
66a23715b9 | ||
|
|
0e24e1cd63 | ||
|
|
a4f3d44ac7 | ||
|
|
68e54eec31 | ||
|
|
47859a9119 | ||
|
|
9ecc5f6f3c | ||
|
|
4ace4b89f6 | ||
|
|
9af60bcccf | ||
|
|
6bc28b894f | ||
|
|
30f2df407a | ||
|
|
f99aae23f0 | ||
|
|
c0ed6f9f4e | ||
|
|
aaa35aefb6 | ||
|
|
f902057c2f | ||
|
|
4a8b4c6e32 | ||
|
|
9b165377ce | ||
|
|
65d6aed6fa | ||
|
|
166903b156 | ||
|
|
ee3f3b1844 | ||
|
|
82c54290ff | ||
|
|
a6f3fadc2c | ||
|
|
62dfd23d75 | ||
|
|
9e5f2a8577 | ||
|
|
5d2f5286c3 | ||
|
|
cc78a8623d | ||
|
|
31f5621fee | ||
|
|
905c6a1dc2 | ||
|
|
5813942580 | ||
|
|
61bad0c6c0 | ||
|
|
22167df44a | ||
|
|
06ac9114fc | ||
|
|
818afe5058 | ||
|
|
1bb8dcd32e | ||
|
|
4eb64b919d | ||
|
|
dbecd2dabd | ||
|
|
6afc18ff5f | ||
|
|
379869c817 | ||
|
|
5a46e95699 | ||
|
|
ee071ed703 | ||
|
|
451fdf47d0 | ||
|
|
9249ac7854 | ||
|
|
34ccb34b1e | ||
|
|
970d88d2f1 | ||
|
|
b4b607bf49 | ||
|
|
67158c56b4 | ||
|
|
9dd33cadfc | ||
|
|
1e901fb632 | ||
|
|
37648b46db | ||
|
|
114e2f80ce | ||
|
|
ab3e15f5d0 | ||
|
|
b3cc66422c | ||
|
|
65ec73c803 | ||
|
|
1d6757e4c9 | ||
|
|
be4fa07d70 | ||
|
|
771d9c5ade | ||
|
|
95cec4e49b | ||
|
|
a12041ee8b | ||
|
|
6c0b1c8ba8 | ||
|
|
1adc9a9129 | ||
|
|
7512986748 | ||
|
|
3faf43c65a | ||
|
|
cf22f5c55c | ||
|
|
4b3de7d9d8 | ||
|
|
37dda460a8 | ||
|
|
8cdb379489 | ||
|
|
1517b392ac | ||
|
|
dde867b19d | ||
|
|
f17f7ec8d1 | ||
|
|
c449d55327 | ||
|
|
16762d9f71 | ||
|
|
7babc8709d | ||
|
|
9d23620486 | ||
|
|
e54ef18f00 | ||
|
|
42a4d9caba | ||
|
|
b4ab85b7f7 | ||
|
|
0ca0ee7d1f | ||
|
|
68d69640a7 | ||
|
|
94ea6c9a11 | ||
|
|
d8ecce8760 | ||
|
|
6b6636deb3 | ||
|
|
b76061f6d8 | ||
|
|
7eb8a437b5 | ||
|
|
05df77cdbe | ||
|
|
8d42c07608 | ||
|
|
df67f8c2c6 | ||
|
|
b895e283a8 | ||
|
|
fa3f7715a3 | ||
|
|
69377622cc | ||
|
|
6f7dc38f2a | ||
|
|
7a56870184 | ||
|
|
cec02f3579 | ||
|
|
b925effd22 | ||
|
|
59dcc44ea6 | ||
|
|
03a231ec54 | ||
|
|
9d695615c4 | ||
|
|
85ac95caf3 | ||
|
|
c96f145246 | ||
|
|
9036d7ae8a | ||
|
|
a4504d5d38 | ||
|
|
87556f8e9e | ||
|
|
eaaa513096 | ||
|
|
a2e64e2abd | ||
|
|
4fd4fa37b3 | ||
|
|
052c82545f | ||
|
|
6f9de43e51 | ||
|
|
5d9f17f71c |
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.50-SNAPSHOT</version>
|
||||
<version>1.6.50</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -119,10 +119,26 @@
|
||||
<opts>
|
||||
<opt>-Dfile.encoding=UTF-8</opt>
|
||||
<opt>--add-opens java.base/java.lang=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.base/java.math=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.base/jdk.internal.misc=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.base/java.nio=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens=java.base/sun.nio.ch=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.base/java.util=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.base/java.lang.reflect=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.base/java.text=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.desktop/java.awt=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.desktop/java.awt.font=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.desktop/java.awt.image=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.desktop/java.awt.color=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.desktop/sun.awt.image=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.desktop/javax.swing=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.desktop/javax.swing.border=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.desktop/javax.swing.event=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.desktop/sun.swing=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.desktop/java.beans=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.base/java.util.concurrent=ALL-UNNAMED</opt>
|
||||
<opt>--add-opens java.base/java.net=ALL-UNNAMED</opt>
|
||||
<opt>-Dio.netty.tryReflectionSetAccessible=true</opt>
|
||||
</opts>
|
||||
</jre>
|
||||
<versionInfo>
|
||||
@@ -214,30 +230,30 @@
|
||||
<dependency>
|
||||
<groupId>com.badlogicgames.gdx</groupId>
|
||||
<artifactId>gdx</artifactId>
|
||||
<version>1.10.0</version>
|
||||
<version>1.11.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.badlogicgames.gdx</groupId>
|
||||
<artifactId>gdx-platform</artifactId>
|
||||
<version>1.10.0</version>
|
||||
<version>1.11.0</version>
|
||||
<classifier>natives-desktop</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.badlogicgames.gdx</groupId>
|
||||
<artifactId>gdx-freetype</artifactId>
|
||||
<version>1.10.0</version>
|
||||
<version>1.11.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.badlogicgames.gdx</groupId>
|
||||
<artifactId>gdx-backend-lwjgl3</artifactId>
|
||||
<version>1.10.0</version>
|
||||
<version>1.11.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.badlogicgames.gdx</groupId>
|
||||
<artifactId>gdx-freetype-platform</artifactId>
|
||||
<version>1.10.0</version>
|
||||
<version>1.11.0</version>
|
||||
<classifier>natives-desktop</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -274,7 +290,7 @@
|
||||
<dependency>
|
||||
<groupId>forge</groupId>
|
||||
<artifactId>forge-gui-mobile</artifactId>
|
||||
<version>1.6.50-SNAPSHOT</version>
|
||||
<version>1.6.50</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -10,7 +10,7 @@ java -version 1>nul 2>nul || (
|
||||
for /f tokens^=2^ delims^=.-_^+^" %%j in ('java -fullversion 2^>^&1') do set "jver=%%j"
|
||||
|
||||
if %jver% GEQ 17 (
|
||||
java --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$
|
||||
java --add-opens java.desktop/java.beans=ALL-UNNAMED --add-opens java.desktop/javax.swing.border=ALL-UNNAMED --add-opens java.desktop/javax.swing.event=ALL-UNNAMED --add-opens java.desktop/sun.swing=ALL-UNNAMED --add-opens java.desktop/java.awt.image=ALL-UNNAMED --add-opens java.desktop/java.awt.color=ALL-UNNAMED --add-opens java.desktop/sun.awt.image=ALL-UNNAMED --add-opens java.desktop/javax.swing=ALL-UNNAMED --add-opens java.desktop/java.awt=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED -Dio.netty.tryReflectionSetAccessible=true -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$
|
||||
popd
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.50-SNAPSHOT</version>
|
||||
<version>1.6.50</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
|
||||
@@ -20,7 +20,6 @@ package forge.ai;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import forge.game.staticability.StaticAbilityMustAttack;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
@@ -47,6 +46,7 @@ import forge.game.combat.GlobalAttackRestrictions;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityPredicates;
|
||||
import forge.game.trigger.Trigger;
|
||||
@@ -58,9 +58,6 @@ import forge.util.Expressions;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollection;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import forge.util.maps.LinkedHashMapToAmount;
|
||||
import forge.util.maps.MapToAmount;
|
||||
import forge.util.maps.MapToAmountUtil;
|
||||
|
||||
|
||||
/**
|
||||
@@ -75,7 +72,7 @@ public class AiAttackController {
|
||||
|
||||
// possible attackers and blockers
|
||||
private List<Card> attackers;
|
||||
private final List<Card> blockers;
|
||||
private List<Card> blockers;
|
||||
|
||||
private List<Card> oppList; // holds human player creatures
|
||||
private List<Card> myList; // holds computer creatures
|
||||
@@ -99,11 +96,9 @@ public class AiAttackController {
|
||||
public AiAttackController(final Player ai, boolean nextTurn) {
|
||||
this.ai = ai;
|
||||
defendingOpponent = choosePreferredDefenderPlayer(ai);
|
||||
this.oppList = getOpponentCreatures(defendingOpponent);
|
||||
myList = ai.getCreaturesInPlay();
|
||||
this.nextTurn = nextTurn;
|
||||
refreshAttackers(defendingOpponent);
|
||||
this.blockers = getPossibleBlockers(oppList, this.attackers, this.nextTurn);
|
||||
refreshCombatants(defendingOpponent);
|
||||
} // overloaded constructor to evaluate attackers that should attack next turn
|
||||
|
||||
public AiAttackController(final Player ai, Card attacker) {
|
||||
@@ -119,13 +114,15 @@ public class AiAttackController {
|
||||
this.blockers = getPossibleBlockers(oppList, this.attackers, this.nextTurn);
|
||||
} // overloaded constructor to evaluate single specified attacker
|
||||
|
||||
private void refreshAttackers(GameEntity defender) {
|
||||
private void refreshCombatants(GameEntity defender) {
|
||||
this.oppList = getOpponentCreatures(defendingOpponent);
|
||||
this.attackers = new ArrayList<>();
|
||||
for (Card c : myList) {
|
||||
if (canAttackWrapper(c, defender)) {
|
||||
attackers.add(c);
|
||||
}
|
||||
}
|
||||
this.blockers = getPossibleBlockers(oppList, this.attackers, this.nextTurn);
|
||||
}
|
||||
|
||||
public static List<Card> getOpponentCreatures(final Player defender) {
|
||||
@@ -153,6 +150,7 @@ public class AiAttackController {
|
||||
|
||||
public void removeBlocker(Card blocker) {
|
||||
this.oppList.remove(blocker);
|
||||
this.blockers.remove(blocker);
|
||||
}
|
||||
|
||||
private boolean canAttackWrapper(final Card attacker, final GameEntity defender) {
|
||||
@@ -170,10 +168,14 @@ public class AiAttackController {
|
||||
public static Player choosePreferredDefenderPlayer(Player ai) {
|
||||
Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range
|
||||
|
||||
// TODO connect with evaluateBoardPosition and only fall back to random when no player is the biggest threat by a fair margin
|
||||
// TODO for multiplayer combat avoid players with cantLose or (if not playing infect) cantLoseForZeroOrLessLife and !canLoseLife
|
||||
|
||||
if (defender.getLife() > 8) {
|
||||
// TODO connect with evaluateBoardPosition and only fall back to random when no player is the biggest threat by a fair margin
|
||||
|
||||
if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players
|
||||
// TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked
|
||||
|
||||
//Otherwise choose a random opponent to ensure no ganging up on players
|
||||
return Aggregates.random(ai.getOpponents());
|
||||
}
|
||||
return defender;
|
||||
@@ -572,8 +574,7 @@ public class AiAttackController {
|
||||
if (numExtraBlocks > 0) {
|
||||
// TODO should be limited to how much getBlockCost the opp can pay
|
||||
while (numExtraBlocks-- > 0 && !remainingAttackers.isEmpty()) {
|
||||
blockedAttackers.add(remainingAttackers.get(0));
|
||||
remainingAttackers.remove(0);
|
||||
blockedAttackers.add(remainingAttackers.remove(0));
|
||||
maxBlockersAfterCrew--;
|
||||
}
|
||||
}
|
||||
@@ -581,8 +582,7 @@ public class AiAttackController {
|
||||
if (remainingAttackers.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
blockedAttackers.add(remainingAttackers.get(0));
|
||||
remainingAttackers.remove(0);
|
||||
blockedAttackers.add(remainingAttackers.remove(0));
|
||||
maxBlockersAfterCrew--;
|
||||
}
|
||||
unblockedAttackers.addAll(remainingAttackers);
|
||||
@@ -605,8 +605,8 @@ public class AiAttackController {
|
||||
final CardCollection unblockableCantPayFor = new CardCollection();
|
||||
final CardCollection unblockableWithoutCost = new CardCollection();
|
||||
// TODO also check poison
|
||||
for (Card attacker : CardLists.getKeyword(unblockedAttackers, Keyword.TRAMPLE)) {
|
||||
Cost tax = CombatUtil.getAttackCost(attacker.getGame(), attacker, defendingOpponent);
|
||||
for (Card attacker : unblockedAttackers) {
|
||||
Cost tax = CombatUtil.getAttackCost(ai.getGame(), attacker, defendingOpponent);
|
||||
if (tax == null) {
|
||||
unblockableWithoutCost.add(attacker);
|
||||
} else {
|
||||
@@ -620,19 +620,17 @@ public class AiAttackController {
|
||||
}
|
||||
}
|
||||
int dmgUnblockableAfterPaying = ComputerUtilCombat.sumDamageIfUnblocked(unblockableWithPaying, defendingOpponent);
|
||||
unblockedAttackers = unblockableWithoutCost;
|
||||
if (dmgUnblockableAfterPaying > trampleDamage) {
|
||||
unblockedAttackers.removeAll(unblockableCantPayFor);
|
||||
unblockedAttackers.removeAll(unblockableWithPaying);
|
||||
myFreeMana -= unblockableAttackTax;
|
||||
totalCombatDamage = dmgUnblockableAfterPaying;
|
||||
// recalculate the trampler damage with the reduced mana available now
|
||||
myFreeMana -= unblockableAttackTax;
|
||||
trampleDamage = getDamageFromBlockingTramplers(blockedAttackers, remainingBlockers, myFreeMana).getLeft();
|
||||
} else {
|
||||
unblockedAttackers = unblockableWithoutCost;
|
||||
myFreeMana -= tramplerTaxPaid;
|
||||
// find out if we can still pay for some left
|
||||
for (Card attacker : unblockableWithPaying) {
|
||||
Cost tax = CombatUtil.getAttackCost(attacker.getGame(), attacker, defendingOpponent);
|
||||
Cost tax = CombatUtil.getAttackCost(ai.getGame(), attacker, defendingOpponent);
|
||||
int taxCMC = tax.getCostMana().getMana().getCMC();
|
||||
if (myFreeMana < unblockableAttackTax + taxCMC) {
|
||||
continue;
|
||||
@@ -664,7 +662,7 @@ public class AiAttackController {
|
||||
CardCollection remainingBlockers = new CardCollection(blockers);
|
||||
for (Card attacker : CardLists.getKeyword(blockedAttackers, Keyword.TRAMPLE)) {
|
||||
// TODO might sort by quotient of dmg/cost for best combination
|
||||
Cost tax = CombatUtil.getAttackCost(attacker.getGame(), attacker, defendingOpponent);
|
||||
Cost tax = CombatUtil.getAttackCost(ai.getGame(), attacker, defendingOpponent);
|
||||
int taxCMC = tax != null ? tax.getCostMana().getMana().getCMC() : 0;
|
||||
if (myFreeMana < currentAttackTax + taxCMC) {
|
||||
continue;
|
||||
@@ -716,12 +714,24 @@ public class AiAttackController {
|
||||
* @return a {@link forge.game.combat.Combat} object.
|
||||
*/
|
||||
public final int declareAttackers(final Combat combat) {
|
||||
// something prevents attacking, try another
|
||||
if (this.attackers.isEmpty() && ai.getOpponents().size() > 1) {
|
||||
final PlayerCollection opps = ai.getOpponents();
|
||||
opps.remove(defendingOpponent);
|
||||
defendingOpponent = Aggregates.random(opps);
|
||||
refreshCombatants(defendingOpponent);
|
||||
}
|
||||
|
||||
final boolean bAssault = doAssault();
|
||||
|
||||
// Determine who will be attacked
|
||||
GameEntity defender = chooseDefender(combat, bAssault);
|
||||
|
||||
// decided to attack another defender so related lists need to be updated
|
||||
// (though usually rather try to avoid this situation for performance reasons)
|
||||
if (defender != defendingOpponent) {
|
||||
refreshAttackers(defender);
|
||||
defendingOpponent = defender instanceof Player ? (Player) defender : ((Card)defender).getController();
|
||||
refreshCombatants(defender);
|
||||
}
|
||||
if (this.attackers.isEmpty()) {
|
||||
return aiAggression;
|
||||
@@ -775,10 +785,7 @@ public class AiAttackController {
|
||||
if (!nextTurn) {
|
||||
for (final Card attacker : this.attackers) {
|
||||
GameEntity mustAttackDef = null;
|
||||
if (attacker.isGoaded()) {
|
||||
// TODO this might result into trying to attack the wrong player
|
||||
mustAttackDef = defender;
|
||||
} else if (attacker.getSVar("MustAttack").equals("True")) {
|
||||
if (attacker.getSVar("MustAttack").equals("True")) {
|
||||
mustAttackDef = defender;
|
||||
} else if (attacker.hasSVar("EndOfTurnLeavePlay")
|
||||
&& isEffectiveAttacker(ai, attacker, combat, defender)) {
|
||||
@@ -787,24 +794,19 @@ public class AiAttackController {
|
||||
//TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack
|
||||
mustAttackDef = defender;
|
||||
} else {
|
||||
final List<GameEntity> attackRequirements = StaticAbilityMustAttack.entitiesMustAttack(attacker);
|
||||
if (attackRequirements.contains(attacker)) {
|
||||
// TODO add cost check here and switch defender if there's one without a cost
|
||||
// must attack anything
|
||||
mustAttackDef = defender;
|
||||
// next check if there's also a specific defender to attack, so don't count them
|
||||
attackRequirements.removeAll(new CardCollection(attacker));
|
||||
}
|
||||
final MapToAmount<GameEntity> amounts = new LinkedHashMapToAmount<>();
|
||||
amounts.addAll(attackRequirements);
|
||||
while (!amounts.isEmpty()) {
|
||||
// check defenders in order of maximum requirements
|
||||
GameEntity mustAttackDefMaybe = MapToAmountUtil.max(amounts).getKey();
|
||||
if (combat.getAttackConstraints().getRequirements().get(attacker) == null) continue;
|
||||
// check defenders in order of maximum requirements
|
||||
for (Pair<GameEntity, Integer> e : combat.getAttackConstraints().getRequirements().get(attacker).getSortedRequirements()) {
|
||||
if (e.getRight() == 0) continue;
|
||||
GameEntity mustAttackDefMaybe = e.getLeft();
|
||||
// Gideon Jura returns LKI
|
||||
if (mustAttackDefMaybe instanceof Card) {
|
||||
mustAttackDefMaybe = ai.getGame().getCardState((Card) mustAttackDefMaybe);
|
||||
}
|
||||
if (canAttackWrapper(attacker, mustAttackDefMaybe) && CombatUtil.getAttackCost(ai.getGame(), attacker, mustAttackDefMaybe) == null) {
|
||||
mustAttackDef = mustAttackDefMaybe;
|
||||
break;
|
||||
}
|
||||
amounts.remove(mustAttackDefMaybe);
|
||||
}
|
||||
}
|
||||
if (mustAttackDef != null) {
|
||||
@@ -926,8 +928,6 @@ public class AiAttackController {
|
||||
final List<Card> nextTurnAttackers = new ArrayList<>();
|
||||
int candidateCounterAttackDamage = 0;
|
||||
|
||||
final Player opp = defender instanceof Player ? (Player) defender : ((Card)defender).getController();
|
||||
this.oppList = getOpponentCreatures(opp);
|
||||
// get the potential damage and strength of the AI forces
|
||||
final List<Card> candidateAttackers = new ArrayList<>();
|
||||
int candidateUnblockedDamage = 0;
|
||||
@@ -936,7 +936,7 @@ public class AiAttackController {
|
||||
// turn, assume summoning sickness creatures will be able to
|
||||
if (ComputerUtilCombat.canAttackNextTurn(pCard) && pCard.getNetCombatDamage() > 0) {
|
||||
candidateAttackers.add(pCard);
|
||||
candidateUnblockedDamage += ComputerUtilCombat.damageIfUnblocked(pCard, opp, null, false);
|
||||
candidateUnblockedDamage += ComputerUtilCombat.damageIfUnblocked(pCard, defendingOpponent, null, false);
|
||||
computerForces++;
|
||||
}
|
||||
}
|
||||
@@ -978,7 +978,7 @@ public class AiAttackController {
|
||||
// find the potential damage ratio the AI can cause
|
||||
double humanLifeToDamageRatio = 1000000;
|
||||
if (candidateUnblockedDamage > 0) {
|
||||
humanLifeToDamageRatio = (double) (opp.getLife() - ComputerUtil.possibleNonCombatDamage(ai, opp)) / candidateUnblockedDamage;
|
||||
humanLifeToDamageRatio = (double) (defendingOpponent.getLife() - ComputerUtil.possibleNonCombatDamage(ai, defendingOpponent)) / candidateUnblockedDamage;
|
||||
}
|
||||
|
||||
// determine if the ai outnumbers the player
|
||||
@@ -1003,7 +1003,7 @@ public class AiAttackController {
|
||||
// get list of attackers ordered from low power to high
|
||||
CardLists.sortByPowerAsc(this.attackers);
|
||||
// get player life total
|
||||
int humanLife = opp.getLife();
|
||||
int humanLife = defendingOpponent.getLife();
|
||||
// get the list of attackers up to the first blocked one
|
||||
final List<Card> attritionalAttackers = new ArrayList<>();
|
||||
for (int x = 0; x < (this.attackers.size() - humanForces); x++) {
|
||||
@@ -1053,7 +1053,7 @@ public class AiAttackController {
|
||||
}
|
||||
}
|
||||
if (isUnblockableCreature) {
|
||||
unblockableDamage += ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat, false);
|
||||
unblockableDamage += ComputerUtilCombat.damageIfUnblocked(attacker, defendingOpponent, combat, false);
|
||||
}
|
||||
}
|
||||
for (final Card attacker : nextTurnAttackers) {
|
||||
@@ -1067,13 +1067,13 @@ public class AiAttackController {
|
||||
}
|
||||
}
|
||||
if (isUnblockableCreature) {
|
||||
nextUnblockableDamage += ComputerUtilCombat.damageIfUnblocked(attacker, opp, null, false);
|
||||
nextUnblockableDamage += ComputerUtilCombat.damageIfUnblocked(attacker, defendingOpponent, null, false);
|
||||
}
|
||||
}
|
||||
if (unblockableDamage > 0 && !opp.cantLoseForZeroOrLessLife() && opp.canLoseLife()) {
|
||||
turnsUntilDeathByUnblockable = 1 + (opp.getLife() - unblockableDamage) / nextUnblockableDamage;
|
||||
if (unblockableDamage > 0 && !defendingOpponent.cantLoseForZeroOrLessLife() && defendingOpponent.canLoseLife()) {
|
||||
turnsUntilDeathByUnblockable = 1 + (defendingOpponent.getLife() - unblockableDamage) / nextUnblockableDamage;
|
||||
}
|
||||
if (opp.canLoseLife()) {
|
||||
if (defendingOpponent.canLoseLife()) {
|
||||
doUnblockableAttack = true;
|
||||
}
|
||||
// *****************
|
||||
@@ -1130,8 +1130,8 @@ public class AiAttackController {
|
||||
if ( LOG_AI_ATTACKS )
|
||||
System.out.println("attackersLeft = " + attackersLeft);
|
||||
|
||||
FCollection<GameEntity> possibleDefenders = new FCollection<>(opp);
|
||||
possibleDefenders.addAll(opp.getPlaneswalkersInPlay());
|
||||
FCollection<GameEntity> possibleDefenders = new FCollection<>(defendingOpponent);
|
||||
possibleDefenders.addAll(defendingOpponent.getPlaneswalkersInPlay());
|
||||
|
||||
while (!attackersLeft.isEmpty()) {
|
||||
CardCollection attackersAssigned = new CardCollection();
|
||||
@@ -1180,7 +1180,7 @@ public class AiAttackController {
|
||||
if (pwDefending.isEmpty()) {
|
||||
// TODO for now only looks at same player as we'd have to check the others from start too
|
||||
//defender = new PlayerCollection(Iterables.filter(possibleDefenders, Player.class)).min(PlayerPredicates.compareByLife());
|
||||
defender = opp;
|
||||
defender = defendingOpponent;
|
||||
} else {
|
||||
final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending);
|
||||
defender = pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending);
|
||||
|
||||
@@ -945,6 +945,39 @@ public class AiBlockController {
|
||||
}
|
||||
}
|
||||
|
||||
private void makeRequiredBlocks(Combat combat) {
|
||||
// assign blockers that have to block
|
||||
final CardCollection chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able.");
|
||||
// if an attacker with lure attacks - all that can block
|
||||
for (final Card blocker : blockersLeft) {
|
||||
if (CombatUtil.mustBlockAnAttacker(blocker, combat, null)) {
|
||||
chumpBlockers.add(blocker);
|
||||
}
|
||||
}
|
||||
if (!chumpBlockers.isEmpty()) {
|
||||
for (final Card attacker : attackers) {
|
||||
List<Card> blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false);
|
||||
for (final Card blocker : blockers) {
|
||||
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
|
||||
&& (CombatUtil.mustBlockAnAttacker(blocker, combat, null)
|
||||
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
|
||||
combat.addBlocker(attacker, blocker);
|
||||
if (!blocker.getMustBlockCards().isEmpty()) {
|
||||
int mustBlockAmt = blocker.getMustBlockCards().size();
|
||||
final CardCollectionView blockedSoFar = combat.getAttackersBlockedBy(blocker);
|
||||
boolean canBlockAnother = CombatUtil.canBlockMoreCreatures(blocker, blockedSoFar);
|
||||
if (!canBlockAnother || mustBlockAmt == blockedSoFar.size()) {
|
||||
blockersLeft.remove(blocker);
|
||||
}
|
||||
} else {
|
||||
blockersLeft.remove(blocker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clearBlockers(final Combat combat, final List<Card> possibleBlockers) {
|
||||
for (final Card blocker : CardLists.filterControlledBy(combat.getAllBlockers(), ai)) {
|
||||
// don't touch other player's blockers
|
||||
@@ -1008,9 +1041,6 @@ public class AiBlockController {
|
||||
|
||||
clearBlockers(combat, possibleBlockers);
|
||||
|
||||
List<Card> blockers;
|
||||
List<Card> chumpBlockers;
|
||||
|
||||
diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade
|
||||
if (ai.getController().isAI() && diff > 0 && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.PLAY_AGGRO)) {
|
||||
diff = 0;
|
||||
@@ -1106,37 +1136,9 @@ public class AiBlockController {
|
||||
}
|
||||
}
|
||||
|
||||
// assign blockers that have to block
|
||||
chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able.");
|
||||
// if an attacker with lure attacks - all that can block
|
||||
for (final Card blocker : blockersLeft) {
|
||||
if (CombatUtil.mustBlockAnAttacker(blocker, combat, null)) {
|
||||
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, null)
|
||||
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
|
||||
combat.addBlocker(attacker, blocker);
|
||||
if (!blocker.getMustBlockCards().isEmpty()) {
|
||||
int mustBlockAmt = blocker.getMustBlockCards().size();
|
||||
final CardCollectionView blockedSoFar = combat.getAttackersBlockedBy(blocker);
|
||||
boolean canBlockAnother = CombatUtil.canBlockMoreCreatures(blocker, blockedSoFar);
|
||||
if (!canBlockAnother || mustBlockAmt == blockedSoFar.size()) {
|
||||
blockersLeft.remove(blocker);
|
||||
}
|
||||
} else {
|
||||
blockersLeft.remove(blocker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// block requirements
|
||||
// TODO because this isn't done earlier, sometimes a good block will enforce a restriction that prevents another for the requirement
|
||||
makeRequiredBlocks(combat);
|
||||
|
||||
// check to see if it's possible to defend a Planeswalker under attack with a chump block,
|
||||
// unless life is low enough to be more worried about saving preserving the life total
|
||||
@@ -1186,8 +1188,7 @@ public class AiBlockController {
|
||||
* Orders a blocker that put onto the battlefield blocking. Depends heavily
|
||||
* on the implementation of orderBlockers().
|
||||
*/
|
||||
public static CardCollection orderBlocker(final Card attacker, final Card blocker,
|
||||
final CardCollection oldBlockers) {
|
||||
public static CardCollection orderBlocker(final Card attacker, final Card blocker, final CardCollection oldBlockers) {
|
||||
// add blocker to existing ordering
|
||||
// sort by evaluate, then insert it appropriately
|
||||
// relies on current implementation of orderBlockers()
|
||||
|
||||
@@ -88,6 +88,13 @@ public class AiController {
|
||||
private SpellAbilityPicker simPicker;
|
||||
private int lastAttackAggression;
|
||||
|
||||
public AiController(final Player computerPlayer, final Game game0) {
|
||||
player = computerPlayer;
|
||||
game = game0;
|
||||
memory = new AiCardMemory();
|
||||
simPicker = new SpellAbilityPicker(game, player);
|
||||
}
|
||||
|
||||
public boolean canCheatShuffle() {
|
||||
return cheatShuffle;
|
||||
}
|
||||
@@ -141,13 +148,6 @@ public class AiController {
|
||||
return predictedCombatNextTurn;
|
||||
}
|
||||
|
||||
public AiController(final Player computerPlayer, final Game game0) {
|
||||
player = computerPlayer;
|
||||
game = game0;
|
||||
memory = new AiCardMemory();
|
||||
simPicker = new SpellAbilityPicker(game, player);
|
||||
}
|
||||
|
||||
private List<SpellAbility> getPossibleETBCounters() {
|
||||
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand));
|
||||
CardCollectionView ccvPlayerLibrary = player.getCardsIn(ZoneType.Library);
|
||||
@@ -709,9 +709,10 @@ public class AiController {
|
||||
return AiPlayDecision.CantPlaySa;
|
||||
}
|
||||
|
||||
final Card host = sa.getHostCard();
|
||||
|
||||
// Check a predefined condition
|
||||
if (sa.hasParam("AICheckSVar")) {
|
||||
final Card host = sa.getHostCard();
|
||||
final String svarToCheck = sa.getParam("AICheckSVar");
|
||||
String comparator = "GE";
|
||||
int compareTo = 1;
|
||||
@@ -734,7 +735,7 @@ public class AiController {
|
||||
}
|
||||
|
||||
int oldCMC = -1;
|
||||
boolean xCost = sa.costHasX() || sa.getHostCard().hasStartOfKeyword("Strive");
|
||||
boolean xCost = sa.costHasX() || host.hasStartOfKeyword("Strive");
|
||||
if (!xCost) {
|
||||
if (!ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) {
|
||||
// for most costs, it's OK to check if they can be paid early in order to avoid running a heavy API check
|
||||
@@ -748,15 +749,15 @@ public class AiController {
|
||||
}
|
||||
|
||||
// state needs to be switched here so API checks evaluate the right face
|
||||
CardStateName currentState = sa.getCardState() != null && sa.getHostCard().getCurrentStateName() != sa.getCardStateName() && !sa.getHostCard().isInPlay() ? sa.getHostCard().getCurrentStateName() : null;
|
||||
CardStateName currentState = sa.getCardState() != null && host.getCurrentStateName() != sa.getCardStateName() && !host.isInPlay() ? host.getCurrentStateName() : null;
|
||||
if (currentState != null) {
|
||||
sa.getHostCard().setState(sa.getCardStateName(), false);
|
||||
host.setState(sa.getCardStateName(), false);
|
||||
}
|
||||
|
||||
AiPlayDecision canPlay = canPlaySa(sa); // this is the "heaviest" check, which also sets up targets, defines X, etc.
|
||||
|
||||
if (currentState != null) {
|
||||
sa.getHostCard().setState(currentState, false);
|
||||
host.setState(currentState, false);
|
||||
}
|
||||
|
||||
if (canPlay != AiPlayDecision.WillPlay) {
|
||||
@@ -766,10 +767,10 @@ public class AiController {
|
||||
// Account for possible Ward after the spell is fully targeted
|
||||
// TODO: ideally, this should be done while targeting, so that a different target can be preferred if the best
|
||||
// one is warded and can't be paid for.
|
||||
if (sa.usesTargeting()) {
|
||||
if (sa.usesTargeting() && CardFactoryUtil.isCounterable(host)) {
|
||||
for (Card tgt : sa.getTargets().getTargetCards()) {
|
||||
// TODO some older cards don't use the keyword, so check for trigger instead
|
||||
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
|
||||
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(host.getController())) {
|
||||
int amount = 0;
|
||||
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
||||
if (wardCost.hasManaCost()) {
|
||||
@@ -1772,7 +1773,7 @@ public class AiController {
|
||||
}
|
||||
return toExile;
|
||||
}
|
||||
|
||||
|
||||
public boolean doTrigger(SpellAbility spell, boolean mandatory) {
|
||||
if (spell instanceof WrappedAbility)
|
||||
return doTrigger(((WrappedAbility)spell).getWrappedAbility(), mandatory);
|
||||
@@ -1784,7 +1785,7 @@ public class AiController {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ai should run.
|
||||
*
|
||||
@@ -1839,12 +1840,11 @@ public class AiController {
|
||||
} else if (effect.hasParam("AICheckDredge")) {
|
||||
return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac");
|
||||
} else return sa != null && doTrigger(sa, false);
|
||||
|
||||
}
|
||||
|
||||
public List<SpellAbility> chooseSaToActivateFromOpeningHand(List<SpellAbility> usableFromOpeningHand) {
|
||||
// AI would play everything. But limits to one copy of (Leyline of Singularity) and (Gemstone Caverns)
|
||||
|
||||
|
||||
List<SpellAbility> result = Lists.newArrayList();
|
||||
for (SpellAbility sa : usableFromOpeningHand) {
|
||||
// Is there a better way for the AI to decide this?
|
||||
@@ -1852,7 +1852,7 @@ public class AiController {
|
||||
result.add(sa);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
boolean hasLeyline1 = false;
|
||||
SpellAbility saGemstones = null;
|
||||
|
||||
@@ -1878,7 +1878,7 @@ public class AiController {
|
||||
result.remove(saGemstones);
|
||||
result.add(saGemstones);
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -329,9 +329,6 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
AbilityUtils.resolve(sa);
|
||||
|
||||
// destroys creatures if they have lethal damage, etc..
|
||||
//game.getAction().checkStateEffects();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,7 +824,7 @@ public class ComputerUtil {
|
||||
}
|
||||
}
|
||||
|
||||
if ("DesecrationDemon".equals(source.getParam("AILogic"))) {
|
||||
if ("DesecrationDemon".equals(logic)) {
|
||||
sacThreshold = SpecialCardAi.DesecrationDemon.getSacThreshold();
|
||||
} else if (considerSacThreshold != -1) {
|
||||
sacThreshold = considerSacThreshold;
|
||||
@@ -1077,7 +1074,7 @@ public class ComputerUtil {
|
||||
playNow = false;
|
||||
break;
|
||||
}
|
||||
if (!playNow && c.isCreature() && ComputerUtilCombat.canAttackNextTurn(c) && c.canBeAttached(card)) {
|
||||
if (!playNow && c.isCreature() && ComputerUtilCombat.canAttackNextTurn(c) && c.canBeAttached(card, null)) {
|
||||
playNow = true;
|
||||
}
|
||||
}
|
||||
@@ -1227,12 +1224,12 @@ public class ComputerUtil {
|
||||
final Game game = sa.getActivatingPlayer().getGame();
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
|
||||
return (sa.getHostCard().isCreature()
|
||||
return sa.getHostCard().isCreature()
|
||||
&& sa.getPayCosts().hasTapCost()
|
||||
&& (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& !ph.getNextTurn().equals(sa.getActivatingPlayer()))
|
||||
&& !sa.getHostCard().hasSVar("EndOfTurnLeavePlay")
|
||||
&& !sa.hasParam("ActivationPhases"));
|
||||
&& !sa.hasParam("ActivationPhases");
|
||||
}
|
||||
|
||||
//returns true if it's better to wait until blockers are declared).
|
||||
@@ -2601,8 +2598,7 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
public static CardCollection getSafeTargets(final Player ai, SpellAbility sa, CardCollectionView validCards) {
|
||||
CardCollection safeCards = new CardCollection(validCards);
|
||||
safeCards = CardLists.filter(safeCards, new Predicate<Card>() {
|
||||
CardCollection safeCards = CardLists.filter(validCards, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (c.getController() == ai) {
|
||||
@@ -2615,8 +2611,7 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
public static Card getKilledByTargeting(final SpellAbility sa, CardCollectionView validCards) {
|
||||
CardCollection killables = new CardCollection(validCards);
|
||||
killables = CardLists.filter(killables, new Predicate<Card>() {
|
||||
CardCollection killables = CardLists.filter(validCards, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.getController() != sa.getActivatingPlayer() && c.getSVar("Targeting").equals("Dies");
|
||||
|
||||
@@ -1181,11 +1181,6 @@ public class ComputerUtilCombat {
|
||||
public static int predictPowerBonusOfAttacker(final Card attacker, final Card blocker, final Combat combat, boolean withoutAbilities, boolean withoutCombatStaticAbilities) {
|
||||
int power = 0;
|
||||
|
||||
//check Exalted only for the first attacker
|
||||
if (combat != null && combat.getAttackers().isEmpty()) {
|
||||
power += attacker.getController().countExaltedBonus();
|
||||
}
|
||||
|
||||
// Serene Master switches power with attacker
|
||||
if (blocker!= null && blocker.getName().equals("Serene Master")) {
|
||||
power += blocker.getNetPower() - attacker.getNetPower();
|
||||
@@ -1268,21 +1263,17 @@ public class ComputerUtilCombat {
|
||||
}
|
||||
|
||||
List<Card> list = Lists.newArrayList();
|
||||
if (!sa.hasParam("ValidCards")) {
|
||||
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), null);
|
||||
}
|
||||
if (sa.hasParam("Defined") && sa.getParam("Defined").startsWith("TriggeredAttacker")) {
|
||||
list.add(attacker);
|
||||
}
|
||||
if (sa.hasParam("ValidCards")) {
|
||||
if (attacker.isValid(sa.getParam("ValidCards").split(","), source.getController(), source, null)
|
||||
|| attacker.isValid(sa.getParam("ValidCards").replace("attacking+", "").split(","),
|
||||
source.getController(), source, null)) {
|
||||
list.add(attacker);
|
||||
}
|
||||
} else {
|
||||
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), null);
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
continue;
|
||||
if (sa.hasParam("Defined") && sa.getParam("Defined").startsWith("TriggeredAttacker")) {
|
||||
list.add(attacker);
|
||||
}
|
||||
if (!list.contains(attacker)) {
|
||||
continue;
|
||||
@@ -1381,11 +1372,6 @@ public class ComputerUtilCombat {
|
||||
, boolean withoutAbilities, boolean withoutCombatStaticAbilities) {
|
||||
int toughness = 0;
|
||||
|
||||
//check Exalted only for the first attacker
|
||||
if (combat != null && combat.getAttackers().isEmpty()) {
|
||||
toughness += attacker.getController().countExaltedBonus();
|
||||
}
|
||||
|
||||
if (blocker != null && attacker.getName().equals("Shape Stealer")) {
|
||||
toughness += blocker.getNetToughness() - attacker.getNetToughness();
|
||||
}
|
||||
@@ -1464,14 +1450,15 @@ public class ComputerUtilCombat {
|
||||
toughness -= predictDamageTo(attacker, damage, source, false);
|
||||
continue;
|
||||
} else if (ApiType.Pump.equals(sa.getApi())) {
|
||||
if (!sa.hasParam("NumDef")) {
|
||||
continue;
|
||||
}
|
||||
if (sa.hasParam("Cost")) {
|
||||
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!sa.hasParam("NumDef")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String defined = sa.getParam("Defined");
|
||||
CardCollection list = AbilityUtils.getDefinedCards(source, defined, sa);
|
||||
if (defined != null && defined.startsWith("TriggeredAttacker")) {
|
||||
@@ -1497,6 +1484,9 @@ public class ComputerUtilCombat {
|
||||
toughness += AbilityUtils.calculateAmount(source, bonus, sa);
|
||||
}
|
||||
} else if (ApiType.PumpAll.equals(sa.getApi())) {
|
||||
if (!sa.hasParam("NumDef")) {
|
||||
continue;
|
||||
}
|
||||
if (sa.hasParam("Cost")) {
|
||||
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
||||
continue;
|
||||
@@ -1506,9 +1496,6 @@ public class ComputerUtilCombat {
|
||||
if (!sa.hasParam("ValidCards")) {
|
||||
continue;
|
||||
}
|
||||
if (!sa.hasParam("NumDef")) {
|
||||
continue;
|
||||
}
|
||||
if (!attacker.isValid(sa.getParam("ValidCards").replace("attacking+", "").split(","), source.getController(), source, sa)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -528,10 +528,11 @@ public class ComputerUtilCost {
|
||||
sa.setActivatingPlayer(player); // complaints on NPE had came before this line was added.
|
||||
}
|
||||
|
||||
final boolean cannotBeCountered = !CardFactoryUtil.isCounterable(sa.getHostCard());
|
||||
|
||||
// Check for stuff like Nether Void
|
||||
int extraManaNeeded = 0;
|
||||
if (sa instanceof Spell) {
|
||||
final boolean cannotBeCountered = !CardFactoryUtil.isCounterable(sa.getHostCard());
|
||||
for (Card c : player.getGame().getCardsIn(ZoneType.Battlefield)) {
|
||||
final String snem = c.getSVar("AI_SpellsNeedExtraMana");
|
||||
if (!StringUtils.isBlank(snem)) {
|
||||
@@ -591,7 +592,7 @@ public class ComputerUtilCost {
|
||||
}
|
||||
|
||||
// Ward - will be accounted for when rechecking a targeted ability
|
||||
if (!(sa instanceof WrappedAbility) && sa.usesTargeting()) {
|
||||
if (!(sa instanceof WrappedAbility) && sa.usesTargeting() && !cannotBeCountered) {
|
||||
for (Card tgt : sa.getTargets().getTargetCards()) {
|
||||
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
|
||||
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
||||
@@ -721,6 +722,17 @@ public class ComputerUtilCost {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ward or human misplay
|
||||
if (ApiType.Counter.equals(sa.getApi())) {
|
||||
List<SpellAbility> spells = AbilityUtils.getDefinedSpellAbilities(source, sa.getParamOrDefault("Defined", "Targeted"), sa);
|
||||
for (SpellAbility toBeCountered : spells) {
|
||||
if (!CardFactoryUtil.isCounterable(toBeCountered.getHostCard())) {
|
||||
return false;
|
||||
}
|
||||
// TODO check hasFizzled
|
||||
}
|
||||
}
|
||||
|
||||
// AI was crashing because the blank ability used to pay costs
|
||||
// Didn't have any of the data on the original SA to pay dependant costs
|
||||
|
||||
|
||||
@@ -656,7 +656,7 @@ public class ComputerUtilMana {
|
||||
ManaPool.refundMana(manaSpentToPay, ai, sa);
|
||||
|
||||
return manaSources;
|
||||
} // getManaSourcesToPayCost()
|
||||
}
|
||||
|
||||
private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, boolean checkPlayable, boolean effect) {
|
||||
AiCardMemory.clearMemorySet(ai, MemorySet.PAYS_TAP_COST);
|
||||
@@ -680,6 +680,8 @@ public class ComputerUtilMana {
|
||||
} else if (mayPlay.hasParam("MayPlayIgnoreType")) {
|
||||
ignoreType = true;
|
||||
}
|
||||
} else if (sa.hasParam("ActivateIgnoreColor")) {
|
||||
ignoreColor = true;
|
||||
}
|
||||
boolean hasConverge = sa.getHostCard().hasConverge();
|
||||
ListMultimap<ManaCostShard, SpellAbility> sourcesForShards = getSourcesForShards(cost, sa, ai, test,
|
||||
@@ -708,7 +710,7 @@ public class ComputerUtilMana {
|
||||
}
|
||||
manapool.applyCardMatrix(pay);
|
||||
|
||||
for (byte color : MagicColor.WUBRGC) {
|
||||
for (byte color : ManaAtom.MANATYPES) {
|
||||
if (manapool.tryPayCostWithColor(color, sa, cost)) {
|
||||
found = true;
|
||||
break;
|
||||
@@ -884,7 +886,7 @@ public class ComputerUtilMana {
|
||||
}
|
||||
|
||||
return true;
|
||||
} // payManaCost()
|
||||
}
|
||||
|
||||
private static void resetPayment(List<SpellAbility> payments) {
|
||||
for (SpellAbility sa : payments) {
|
||||
@@ -958,7 +960,6 @@ public class ComputerUtilMana {
|
||||
|
||||
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);
|
||||
@@ -966,7 +967,7 @@ public class ComputerUtilMana {
|
||||
else if (saPayment.getApi() == ApiType.ManaReflected) {
|
||||
Set<String> reflected = CardUtil.getReflectableManaColors(saPayment);
|
||||
|
||||
for (byte c : MagicColor.WUBRG) {
|
||||
for (byte c : MagicColor.WUBRGC) {
|
||||
if (ai.getManaPool().canPayForShardWithColor(toPay, c) && reflected.contains(MagicColor.toLongString(c))) {
|
||||
m.setExpressChoice(MagicColor.toShortString(c));
|
||||
return;
|
||||
@@ -1039,7 +1040,7 @@ public class ComputerUtilMana {
|
||||
if (ma.getApi() == ApiType.ManaReflected) {
|
||||
Set<String> reflected = CardUtil.getReflectableManaColors(ma);
|
||||
|
||||
for (byte c : MagicColor.WUBRG) {
|
||||
for (byte c : MagicColor.WUBRGC) {
|
||||
if (toPay == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(MagicColor.toShortString(c), xManaCostPaidByColor)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -823,6 +823,7 @@ public abstract class GameState {
|
||||
String id = rememberedEnts.getValue();
|
||||
|
||||
Card exiledWith = idToCard.get(Integer.parseInt(id));
|
||||
exiledWith.addExiledCard(c);
|
||||
c.setExiledWith(exiledWith);
|
||||
c.setExiledBy(exiledWith.getController());
|
||||
}
|
||||
@@ -1134,7 +1135,7 @@ public abstract class GameState {
|
||||
Card attachedTo = idToCard.get(entry.getValue());
|
||||
Card attacher = entry.getKey();
|
||||
if (attacher.isAttachment()) {
|
||||
attacher.attachToEntity(attachedTo);
|
||||
attacher.attachToEntity(attachedTo, null, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1145,7 +1146,7 @@ public abstract class GameState {
|
||||
Game game = attacher.getGame();
|
||||
Player attachedTo = entry.getValue() == TARGET_AI ? game.getPlayers().get(1) : game.getPlayers().get(0);
|
||||
|
||||
attacher.attachToEntity(attachedTo);
|
||||
attacher.attachToEntity(attachedTo, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1341,12 +1342,14 @@ public abstract class GameState {
|
||||
}
|
||||
} else if (info.startsWith("Transformed")) {
|
||||
c.setState(CardStateName.Transformed, true);
|
||||
c.setBackSide(true);
|
||||
} else if (info.startsWith("Flipped")) {
|
||||
c.setState(CardStateName.Flipped, true);
|
||||
} else if (info.startsWith("Meld")) {
|
||||
c.setState(CardStateName.Meld, true);
|
||||
} else if (info.startsWith("Modal")) {
|
||||
c.setState(CardStateName.Modal, true);
|
||||
c.setBackSide(true);
|
||||
}
|
||||
else if (info.startsWith("OnAdventure")) {
|
||||
String abAdventure = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell";
|
||||
|
||||
@@ -727,7 +727,6 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
@@ -43,7 +42,6 @@ import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellPermanent;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
@@ -78,9 +76,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (!ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)
|
||||
&& source.getType().isLegendary() && sa instanceof SpellPermanent
|
||||
&& ai.isCardInPlay(source.getName())) {
|
||||
if (source.isAura() && sa.isSpell() && !source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
|
||||
// Don't play the second copy of a legendary enchantment already in play
|
||||
|
||||
// TODO: Add some extra checks for where the AI may want to cast a replacement aura
|
||||
@@ -618,7 +614,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
CardCollection preList = new CardCollection(lki);
|
||||
preList.add(attachSourceLki);
|
||||
c.getGame().getAction().checkStaticAbilities(false, Sets.newHashSet(preList), preList);
|
||||
boolean result = lki.canBeAttached(attachSourceLki);
|
||||
boolean result = lki.canBeAttached(attachSourceLki, null);
|
||||
|
||||
//reset static abilities
|
||||
c.getGame().getAction().checkStaticAbilities(false);
|
||||
@@ -1354,7 +1350,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
if (tgt == null) {
|
||||
list = AbilityUtils.getDefinedCards(attachSource, sa.getParam("Defined"), sa);
|
||||
} else {
|
||||
list = CardLists.filter(CardUtil.getValidCardsToTarget(tgt, sa), CardPredicates.canBeAttached(attachSource));
|
||||
list = CardLists.filter(CardUtil.getValidCardsToTarget(tgt, sa), CardPredicates.canBeAttached(attachSource, sa));
|
||||
}
|
||||
|
||||
if (list.isEmpty()) {
|
||||
@@ -1609,8 +1605,6 @@ public class AttachAi extends SpellAbilityAi {
|
||||
&& CombatUtil.canBlock(card, true);
|
||||
} else if (keyword.equals("Reach")) {
|
||||
return !card.hasKeyword(Keyword.FLYING) && CombatUtil.canBlock(card, true);
|
||||
} else if (keyword.equals("CARDNAME can attack as though it didn't have defender.")) {
|
||||
return card.hasKeyword(Keyword.DEFENDER) && card.getNetCombatDamage() + powerBonus > 0;
|
||||
} else if (keyword.equals("Shroud") || keyword.equals("Hexproof")) {
|
||||
return !card.hasKeyword(Keyword.SHROUD) && !card.hasKeyword(Keyword.HEXPROOF);
|
||||
} else return !keyword.equals("Defender");
|
||||
@@ -1627,7 +1621,6 @@ public class AttachAi extends SpellAbilityAi {
|
||||
* @return true, if is useful keyword
|
||||
*/
|
||||
private static boolean isUsefulCurseKeyword(final String keyword, final Card card, final SpellAbility sa) {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
@@ -274,13 +273,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
//Ninjutsu
|
||||
if (sa.hasParam("Ninjutsu")) {
|
||||
if (source.getType().isLegendary()
|
||||
&& !ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
|
||||
if (ai.getZone(ZoneType.Battlefield).contains(CardPredicates.nameEquals(source.getName()))) {
|
||||
return false;
|
||||
}
|
||||
if (sa.isNinjutsu()) {
|
||||
if (!source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
|
||||
return false;
|
||||
}
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE)) {
|
||||
return false;
|
||||
@@ -732,18 +727,16 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
// predict Legendary cards already present
|
||||
if (!ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
|
||||
boolean nothingWillReturn = true;
|
||||
for (final Card c : retrieval) {
|
||||
if (!(c.getType().isLegendary() && ai.isCardInPlay(c.getName()))) {
|
||||
nothingWillReturn = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nothingWillReturn) {
|
||||
return false;
|
||||
boolean nothingWillReturn = true;
|
||||
for (final Card c : retrieval) {
|
||||
if (!(!c.ignoreLegendRule() && ai.isCardInPlay(c.getName()))) {
|
||||
nothingWillReturn = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nothingWillReturn) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -214,7 +214,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
private static int calcAmount(final SpellAbility sa, final CounterType cType) {
|
||||
final Card host = sa.getHostCard();
|
||||
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||
|
||||
// TODO handle proper calculation of X values based on Cost
|
||||
int amount = 0;
|
||||
@@ -349,6 +349,9 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} else if (sa.getMaxTargets() == 2) {
|
||||
// TODO
|
||||
return false;
|
||||
} else {
|
||||
// SA uses target for Defined
|
||||
@@ -361,10 +364,8 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
final Card src = srcCards.get(0);
|
||||
if (cType != null) {
|
||||
if (src.getCounters(cType) <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (cType != null && src.getCounters(cType) <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Card lki = CardUtil.getLKICopy(src);
|
||||
|
||||
@@ -341,7 +341,7 @@ public class CountersPutAi extends CountersAi {
|
||||
|
||||
if (sa.hasParam("Bolster")) {
|
||||
CardCollection creatsYouCtrl = ai.getCreaturesInPlay();
|
||||
CardCollection leastToughness = new CardCollection(Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetDefense));
|
||||
CardCollection leastToughness = new CardCollection(Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetNetToughness));
|
||||
if (leastToughness.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -20,11 +20,13 @@ package forge.ai.ability;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
@@ -37,6 +39,7 @@ import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController.BinaryChoiceType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -97,13 +100,19 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
return true;
|
||||
} else {
|
||||
// currently only Clockspinning
|
||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||
|
||||
// logic to remove some counter
|
||||
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounters());
|
||||
|
||||
if (!countersList.isEmpty()) {
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
|
||||
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card input) {
|
||||
return input.ignoreLegendRule();
|
||||
}
|
||||
});
|
||||
if (maritEmpty) {
|
||||
CardCollectionView depthsList = CardLists.filter(countersList,
|
||||
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterEnumType.ICE));
|
||||
|
||||
@@ -197,53 +206,56 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
||||
if (options.size() > 1) {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final Game game = ai.getGame();
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
|
||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||
Card tgt = (Card) params.get("Target");
|
||||
|
||||
Card tgt = (Card) params.get("Target");
|
||||
// planeswalker has high priority for loyalty counters
|
||||
if (tgt.isPlaneswalker() && options.contains(CounterType.get(CounterEnumType.LOYALTY))) {
|
||||
return CounterType.get(CounterEnumType.LOYALTY);
|
||||
}
|
||||
|
||||
// planeswalker has high priority for loyalty counters
|
||||
if (tgt.isPlaneswalker() && options.contains(CounterType.get(CounterEnumType.LOYALTY))) {
|
||||
return CounterType.get(CounterEnumType.LOYALTY);
|
||||
}
|
||||
|
||||
if (tgt.getController().isOpponentOf(ai)) {
|
||||
// creatures with BaseToughness below or equal zero might be
|
||||
// killed if their counters are removed
|
||||
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
|
||||
if (options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
||||
return CounterType.get(CounterEnumType.P1P1);
|
||||
} else if (options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
||||
return CounterType.get(CounterEnumType.M1M1);
|
||||
}
|
||||
}
|
||||
|
||||
// fallback logic, select positive counter to remove it
|
||||
for (final CounterType type : options) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// this counters are treat first to be removed
|
||||
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.get(CounterEnumType.ICE))) {
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
return CounterType.get(CounterEnumType.ICE);
|
||||
}
|
||||
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
||||
if (tgt.getController().isOpponentOf(ai)) {
|
||||
// creatures with BaseToughness below or equal zero might be
|
||||
// killed if their counters are removed
|
||||
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
|
||||
if (options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
||||
return CounterType.get(CounterEnumType.P1P1);
|
||||
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
||||
} else if (options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
||||
return CounterType.get(CounterEnumType.M1M1);
|
||||
}
|
||||
}
|
||||
|
||||
// fallback logic, select positive counter to add more
|
||||
for (final CounterType type : options) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
|
||||
return type;
|
||||
// fallback logic, select positive counter to remove it
|
||||
for (final CounterType type : options) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// this counters are treat first to be removed
|
||||
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.get(CounterEnumType.ICE))) {
|
||||
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
|
||||
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card input) {
|
||||
return input.ignoreLegendRule();
|
||||
}
|
||||
});
|
||||
|
||||
if (maritEmpty) {
|
||||
return CounterType.get(CounterEnumType.ICE);
|
||||
}
|
||||
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
||||
return CounterType.get(CounterEnumType.P1P1);
|
||||
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
||||
return CounterType.get(CounterEnumType.M1M1);
|
||||
}
|
||||
|
||||
// fallback logic, select positive counter to add more
|
||||
for (final CounterType type : options) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,12 +274,10 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
|
||||
if (kindOfChoice.equals(BinaryChoiceType.AddOrRemove)) {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
Card tgt = (Card) params.get("Target");
|
||||
CounterType type = (CounterType) params.get("CounterType");
|
||||
|
||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||
|
||||
if (tgt.getController().isOpponentOf(ai)) {
|
||||
if (type.is(CounterEnumType.LOYALTY) && tgt.isPlaneswalker()) {
|
||||
return false;
|
||||
@@ -276,7 +286,15 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
return ComputerUtil.isNegativeCounter(type, tgt);
|
||||
} else {
|
||||
if (type.is(CounterEnumType.ICE) && "Dark Depths".equals(tgt.getName())) {
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
|
||||
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card input) {
|
||||
return input.ignoreLegendRule();
|
||||
}
|
||||
});
|
||||
|
||||
if (maritEmpty) {
|
||||
return false;
|
||||
}
|
||||
} else if (type.is(CounterEnumType.M1M1) && tgt.hasKeyword(Keyword.PERSIST)) {
|
||||
|
||||
@@ -3,7 +3,9 @@ package forge.ai.ability;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
@@ -11,7 +13,6 @@ import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
@@ -102,7 +103,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
final Game game = ai.getGame();
|
||||
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String amountStr = sa.getParam("CounterNum");
|
||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||
|
||||
// remove counter with Time might use Exile Zone too
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
@@ -117,16 +118,21 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
// Filter AI-specific targets if provided
|
||||
list = ComputerUtil.filterAITgts(sa, ai, list, false);
|
||||
|
||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
|
||||
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card input) {
|
||||
return input.ignoreLegendRule();
|
||||
}
|
||||
});
|
||||
|
||||
if (type.matches("All")) {
|
||||
// Logic Part for Vampire Hexmage
|
||||
// Break Dark Depths
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
if (maritEmpty) {
|
||||
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
||||
CardPredicates.hasCounter(CounterEnumType.ICE, 3));
|
||||
|
||||
if (!depthsList.isEmpty()) {
|
||||
sa.getTargets().add(depthsList.getFirst());
|
||||
return true;
|
||||
@@ -161,7 +167,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
}
|
||||
// try to remove them from Dark Depths and Planeswalkers too
|
||||
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
if (maritEmpty) {
|
||||
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
||||
CardPredicates.hasCounter(CounterEnumType.ICE));
|
||||
@@ -407,9 +413,6 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
|
||||
if (options.size() <= 1) {
|
||||
return super.chooseCounterType(options, sa, params);
|
||||
}
|
||||
Player ai = sa.getActivatingPlayer();
|
||||
GameEntity target = (GameEntity) params.get("Target");
|
||||
|
||||
|
||||
@@ -1075,6 +1075,11 @@ public class DamageDealAi extends DamageAiBase {
|
||||
return null;
|
||||
}
|
||||
|
||||
// chaining to this could miscalculate
|
||||
if (sa.isDividedAsYouChoose()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to chain damage/debuff effects
|
||||
if (StringUtils.isNumeric(damage) || (damage.startsWith("-") && StringUtils.isNumeric(damage.substring(1)))) {
|
||||
// currently only works for predictable numeric damage
|
||||
|
||||
@@ -353,12 +353,10 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
// Filter AI-specific targets if provided
|
||||
preferred = ComputerUtil.filterAITgts(sa, ai, preferred, true);
|
||||
|
||||
for (final Card c : preferred) {
|
||||
list.remove(c);
|
||||
}
|
||||
list.removeAll(preferred);
|
||||
|
||||
if (preferred.isEmpty() && !mandatory) {
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
while (sa.canAddMoreTarget()) {
|
||||
|
||||
@@ -102,7 +102,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source,sa)) {
|
||||
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source, sa)) {
|
||||
AiCostDecision aiDecisions = new AiCostDecision(ai, sa, false);
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (part instanceof CostDiscard) {
|
||||
@@ -508,7 +508,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (numCards >= computerLibrarySize - 3) {
|
||||
if (ai.isCardInPlay("Laboratory Maniac")) {
|
||||
if (ai.isCardInPlay("Laboratory Maniac") && !ai.cantWin()) {
|
||||
return true;
|
||||
}
|
||||
// Don't deck yourself
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Map;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
@@ -20,6 +21,7 @@ public class MutateAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
CardCollectionView mutateTgts = CardLists.getTargetableCards(aiPlayer.getCreaturesInPlay(), sa);
|
||||
mutateTgts = ComputerUtil.getSafeTargets(aiPlayer, sa, mutateTgts);
|
||||
|
||||
// Filter out some abilities that are useless
|
||||
// TODO: add other stuff useless for Mutate here
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.SpellApiToAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
@@ -9,6 +11,7 @@ import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilityStatic;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
@@ -38,9 +41,31 @@ public class PeekAndRevealAi extends SpellAbilityAi {
|
||||
}
|
||||
// So far this only appears on Triggers, but will expand
|
||||
// once things get converted from Dig + NoMove
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||
final Card host = sa.getHostCard();
|
||||
Player libraryOwner = aiPlayer;
|
||||
|
||||
if (!willPayCosts(aiPlayer, sa, sa.getPayCosts(), host)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
//todo: evaluate valid targets
|
||||
if (!sa.canTarget(opp)) {
|
||||
return false;
|
||||
}
|
||||
sa.getTargets().add(opp);
|
||||
libraryOwner = opp;
|
||||
}
|
||||
|
||||
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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)
|
||||
*/
|
||||
|
||||
@@ -11,8 +11,6 @@ import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.CardType.Supertype;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
@@ -52,28 +50,25 @@ public class PermanentAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
// check on legendary
|
||||
if (card.getType().isLegendary()
|
||||
&& !game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
|
||||
if (ai.isCardInPlay(card.getName())) {
|
||||
if (!card.hasSVar("AILegendaryException")) {
|
||||
// AiPlayDecision.WouldDestroyLegend
|
||||
return false;
|
||||
} else {
|
||||
String specialRule = card.getSVar("AILegendaryException");
|
||||
if ("TwoCopiesAllowed".equals(specialRule)) {
|
||||
// One extra copy allowed on the battlefield, e.g. Brothers Yamazaki
|
||||
if (CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(card.getName())) > 1) {
|
||||
return false;
|
||||
}
|
||||
} else if ("AlwaysAllowed".equals(specialRule)) {
|
||||
// Nothing to do here, check for Legendary is disabled
|
||||
} else {
|
||||
// Unknown hint, assume two copies not allowed
|
||||
if (!card.ignoreLegendRule() && ai.isCardInPlay(card.getName())) {
|
||||
// TODO check the risk we'd lose the effect with bad timing
|
||||
if (!card.hasSVar("AILegendaryException")) {
|
||||
// AiPlayDecision.WouldDestroyLegend
|
||||
return false;
|
||||
} else {
|
||||
String specialRule = card.getSVar("AILegendaryException");
|
||||
if ("TwoCopiesAllowed".equals(specialRule)) {
|
||||
// One extra copy allowed on the battlefield, e.g. Brothers Yamazaki
|
||||
if (CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(card.getName())) > 1) {
|
||||
return false;
|
||||
}
|
||||
} else if ("AlwaysAllowed".equals(specialRule)) {
|
||||
// Nothing to do here, check for Legendary is disabled
|
||||
} else {
|
||||
// Unknown hint, assume two copies not allowed
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public class PumpAi extends PumpAiBase {
|
||||
}
|
||||
return cost.hasSpecificCostType(CostTapType.class);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||
if ("MoveCounter".equals(aiLogic)) {
|
||||
@@ -145,6 +145,7 @@ public class PumpAi extends PumpAiBase {
|
||||
}
|
||||
|
||||
final String counterType = moveSA.getParam("CounterType");
|
||||
final String amountStr = moveSA.getParamOrDefault("CounterNum", "1");
|
||||
final CounterType cType = "Any".equals(counterType) ? null : CounterType.getType(counterType);
|
||||
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
@@ -157,7 +158,6 @@ public class PumpAi extends PumpAiBase {
|
||||
if (attr.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final String amountStr = moveSA.getParam("CounterNum");
|
||||
CardCollection best = CardLists.filter(attr, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
@@ -198,7 +198,6 @@ public class PumpAi extends PumpAiBase {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
final String amountStr = moveSA.getParam("CounterNum");
|
||||
final boolean sameCtrl = moveSA.getTargetRestrictions().isSameController();
|
||||
|
||||
List<Card> list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||
@@ -399,7 +398,7 @@ public class PumpAi extends PumpAiBase {
|
||||
|
||||
if (!mandatory
|
||||
&& !immediately
|
||||
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& (game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) && !"AnyPhase".equals(sa.getParam("AILogic")))
|
||||
&& !(sa.isCurse() && defense < 0)
|
||||
&& !containsNonCombatKeyword(keywords)
|
||||
&& !"UntilYourNextTurn".equals(sa.getParam("Duration"))
|
||||
@@ -597,7 +596,7 @@ public class PumpAi extends PumpAiBase {
|
||||
}
|
||||
|
||||
return true;
|
||||
} // pumpTgtAI()
|
||||
}
|
||||
|
||||
private boolean pumpMandatoryTarget(final Player ai, final SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
@@ -658,7 +657,7 @@ public class PumpAi extends PumpAiBase {
|
||||
}
|
||||
|
||||
return true;
|
||||
} // pumpMandatoryTarget()
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
@@ -707,7 +706,7 @@ public class PumpAi extends PumpAiBase {
|
||||
}
|
||||
|
||||
return true;
|
||||
} // pumpTriggerAI
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
|
||||
@@ -779,7 +778,7 @@ public class PumpAi extends PumpAiBase {
|
||||
}
|
||||
|
||||
return true;
|
||||
} // pumpDrawbackAI()
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
|
||||
@@ -399,10 +399,6 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
&& newPower > 0
|
||||
&& !CardLists.getType(opp.getLandsInPlay(), "Forest").isEmpty()
|
||||
&& Iterables.any(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card));
|
||||
} else if (keyword.endsWith("CARDNAME can attack as though it didn't have defender.")) {
|
||||
return ph.isPlayerTurn(ai) && card.hasKeyword(Keyword.DEFENDER)
|
||||
&& !ph.getPhase().isAfter(PhaseType.COMBAT_BEGIN)
|
||||
&& !card.isTapped() && newPower > 0;
|
||||
} else if (keyword.equals("Prevent all combat damage that would be dealt to CARDNAME.")) {
|
||||
return combat != null && (combat.isBlocking(card) || combat.isBlocked(card));
|
||||
} else if (keyword.equals("Menace")) {
|
||||
@@ -428,7 +424,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
}
|
||||
});
|
||||
return list;
|
||||
} // getPumpCreatures()
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -519,7 +515,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
return list;
|
||||
} // getCurseCreatures()
|
||||
}
|
||||
|
||||
protected boolean containsNonCombatKeyword(final List<String> keywords) {
|
||||
for (final String keyword : keywords) {
|
||||
|
||||
@@ -2,10 +2,8 @@ package forge.ai.ability;
|
||||
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.*;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -54,16 +52,16 @@ public class RepeatAi extends SpellAbilityAi {
|
||||
if (sa.usesTargeting()) {
|
||||
if (logic.startsWith("CopyBestCreature")) {
|
||||
Card best = null;
|
||||
if (!ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule) || logic.endsWith("IgnoreLegendary")) {
|
||||
best = ComputerUtilCard.getBestAI(Iterables.filter(ai.getCreaturesInPlay(), Predicates.and(
|
||||
CardPredicates.isTargetableBy(sa), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return !card.getType().isLegendary();
|
||||
}
|
||||
})));
|
||||
Iterable<Card> targetableAi = Iterables.filter(ai.getCreaturesInPlay(), CardPredicates.isTargetableBy(sa));
|
||||
if (!logic.endsWith("IgnoreLegendary")) {
|
||||
best = ComputerUtilCard.getBestAI(Iterables.filter(targetableAi, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return card.ignoreLegendRule();
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
best = ComputerUtilCard.getBestAI(Iterables.filter(ai.getCreaturesInPlay(), CardPredicates.isTargetableBy(sa)));
|
||||
best = ComputerUtilCard.getBestAI(targetableAi);
|
||||
}
|
||||
if (best == null && mandatory && sa.canTarget(sa.getHostCard())) {
|
||||
best = sa.getHostCard();
|
||||
|
||||
@@ -9,7 +9,6 @@ import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.CardStateName;
|
||||
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
@@ -238,7 +237,7 @@ public class SetStateAi extends SpellAbilityAi {
|
||||
private boolean isSafeToTransformIntoLegendary(Player aiPlayer, Card source) {
|
||||
// Prevent transform into legendary creature if copy already exists
|
||||
// Check first if Legend Rule does still apply
|
||||
if (!aiPlayer.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
|
||||
if (!source.ignoreLegendRule()) {
|
||||
if (!source.hasAlternateState()) {
|
||||
System.err.println("Warning: SetState without ALTERNATE on " + source.getName() + ".");
|
||||
return false;
|
||||
|
||||
@@ -106,10 +106,8 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
* @return a boolean.
|
||||
*/
|
||||
protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) {
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
final Game game = ai.getGame();
|
||||
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||
tapList = CardLists.getTargetableCards(tapList, sa);
|
||||
CardCollection tapList = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
|
||||
tapList = CardLists.filter(tapList, Presets.UNTAPPED);
|
||||
tapList = CardLists.filter(tapList, new Predicate<Card>() {
|
||||
@Override
|
||||
@@ -129,8 +127,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
|
||||
//use broader approach when the cost is a positive thing
|
||||
if (tapList.isEmpty() && ComputerUtil.activateForCost(sa, ai)) {
|
||||
tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||
tapList = CardLists.getTargetableCards(tapList, sa);
|
||||
tapList = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
|
||||
tapList = CardLists.filter(tapList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
@@ -150,8 +147,10 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
|
||||
//try to exclude things that will already be tapped due to something on stack or because something is
|
||||
//already targeted in a parent or sub SA
|
||||
CardCollection toExclude = ComputerUtilAbility.getCardsTargetedWithApi(ai, tapList, sa, ApiType.Tap);
|
||||
tapList.removeAll(toExclude);
|
||||
if (!sa.isTrigger() || mandatory) { // but if just confirming trigger no need to look for other targets and might still help anyway
|
||||
CardCollection toExclude = ComputerUtilAbility.getCardsTargetedWithApi(ai, tapList, sa, ApiType.Tap);
|
||||
tapList.removeAll(toExclude);
|
||||
}
|
||||
|
||||
if (tapList.isEmpty()) {
|
||||
return false;
|
||||
@@ -176,6 +175,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
PhaseHandler phase = game.getPhaseHandler();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
Card primeTarget = ComputerUtil.getKilledByTargeting(sa, tapList);
|
||||
if (primeTarget != null) {
|
||||
choice = primeTarget;
|
||||
@@ -193,7 +193,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
return CombatUtil.canAttack(c, opp);
|
||||
}
|
||||
});
|
||||
attackers.remove(sa.getHostCard());
|
||||
attackers.remove(source);
|
||||
}
|
||||
Predicate<Card> findBlockers = CardPredicates.possibleBlockerForAtLeastOne(attackers);
|
||||
List<Card> creatureList = CardLists.filter(tapList, findBlockers);
|
||||
@@ -202,7 +202,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
|
||||
if (!attackers.isEmpty() && !creatureList.isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestCreatureAI(creatureList);
|
||||
} else if (sa.getRootAbility().isTrigger() || ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
} else if (sa.isTrigger() || ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(tapList);
|
||||
}
|
||||
} else if (phase.isPlayerTurn(opp)
|
||||
@@ -272,7 +272,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
// filter by enchantments and planeswalkers, their tapped state doesn't matter.
|
||||
// filter by enchantments and planeswalkers, their tapped state (usually) doesn't matter.
|
||||
final String[] tappablePermanents = { "Enchantment", "Planeswalker" };
|
||||
tapList = CardLists.getValidCards(list, tappablePermanents, source.getController(), source, sa);
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ComputerUtilCost.checkDiscardCost(ai, cost, sa.getHostCard(), sa);
|
||||
return ComputerUtilCost.checkDiscardCost(ai, cost, source, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -133,6 +133,10 @@ public class UntapAi extends SpellAbilityAi {
|
||||
|
||||
CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa);
|
||||
|
||||
if (!sa.isCurse()) {
|
||||
list = ComputerUtil.getSafeTargets(ai, sa, list);
|
||||
}
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
@@ -170,8 +174,10 @@ public class UntapAi extends SpellAbilityAi {
|
||||
|
||||
//try to exclude things that will already be untapped due to something on stack or because something is
|
||||
//already targeted in a parent or sub SA
|
||||
CardCollection toExclude = ComputerUtilAbility.getCardsTargetedWithApi(ai, untapList, sa, ApiType.Untap);
|
||||
untapList.removeAll(toExclude);
|
||||
if (!sa.isTrigger() || mandatory) { // but if just confirming trigger no need to look for other targets and might still help anyway
|
||||
CardCollection toExclude = ComputerUtilAbility.getCardsTargetedWithApi(ai, untapList, sa, ApiType.Untap);
|
||||
untapList.removeAll(toExclude);
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
while (sa.canAddMoreTarget()) {
|
||||
@@ -195,7 +201,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
if (choice == null) {
|
||||
if (CardLists.getNotType(untapList, "Creature").isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestCreatureAI(untapList); // if only creatures take the best
|
||||
} else if (!sa.getPayCosts().hasManaCost() || sa.getRootAbility().isTrigger()
|
||||
} else if (!sa.getPayCosts().hasManaCost() || sa.isTrigger()
|
||||
|| "Always".equals(sa.getParam("AILogic"))) {
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(untapList);
|
||||
}
|
||||
@@ -261,10 +267,8 @@ public class UntapAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
private boolean untapTargetList(final Card source, final TargetRestrictions tgt, final SpellAbility sa, final boolean mandatory,
|
||||
final CardCollection tapList) {
|
||||
for (final Card c : sa.getTargets().getTargetCards()) {
|
||||
tapList.remove(c);
|
||||
}
|
||||
final CardCollection tapList) {
|
||||
tapList.removeAll(sa.getTargets().getTargetCards());
|
||||
|
||||
if (tapList.isEmpty()) {
|
||||
return false;
|
||||
@@ -288,7 +292,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
choice = ComputerUtilCard.getBestAI(tapList);
|
||||
|
||||
if (choice == null) { // can't find anything left
|
||||
if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().size() == 0) {
|
||||
if (sa.getTargets().size() < tgt.getMinTargets(source, sa) || sa.getTargets().size() == 0) {
|
||||
if (!mandatory) {
|
||||
sa.resetTargets();
|
||||
}
|
||||
@@ -308,9 +312,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> list, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
PlayerCollection pl = new PlayerCollection();
|
||||
pl.add(ai);
|
||||
pl.addAll(ai.getAllies());
|
||||
PlayerCollection pl = ai.getYourTeam();
|
||||
return ComputerUtilCard.getBestAI(CardLists.filterControlledBy(list, pl));
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ public class ZoneExchangeAi extends SpellAbilityAi {
|
||||
}
|
||||
if (type.equals("Aura")) {
|
||||
Card c = object1.getEnchantingCard();
|
||||
if (!c.canBeAttached(object2)) {
|
||||
if (!c.canBeAttached(object2, sa)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.50-SNAPSHOT</version>
|
||||
<version>1.6.50</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-core</artifactId>
|
||||
|
||||
@@ -299,20 +299,13 @@ public final class ImageKeys {
|
||||
}
|
||||
private static File findFile(String dir, String filename) {
|
||||
if (dir.equals(CACHE_CARD_PICS_DIR)) {
|
||||
File f = new File(dir+"/"+filename);
|
||||
String initialDirectory = f.getParent();
|
||||
String cardName = f.getName();
|
||||
File parentDir = new File(initialDirectory);
|
||||
String[] cardNames = parentDir.list();
|
||||
if (cardNames != null) {
|
||||
Set<String> cardList = new HashSet<>(Arrays.asList(cardNames));
|
||||
for (String ext : FILE_EXTENSIONS) {
|
||||
if (ext.equals(""))
|
||||
continue;
|
||||
String cardLookup = cardName+ext;
|
||||
if (cardList.contains(cardLookup)) {
|
||||
return new File(parentDir+"/"+cardLookup);
|
||||
}
|
||||
for (String ext : FILE_EXTENSIONS) {
|
||||
if (ext.equals(""))
|
||||
continue;
|
||||
|
||||
File f = new File(dir, filename + ext);
|
||||
if (f.exists()) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -20,6 +20,8 @@ package forge.card;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.*;
|
||||
|
||||
import forge.StaticData;
|
||||
import forge.card.CardEdition.CardInSet;
|
||||
import forge.card.CardEdition.Type;
|
||||
import forge.deck.generation.IDeckGenPool;
|
||||
@@ -45,14 +47,16 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
private final Map<String, PaperCard> uniqueCardsByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
|
||||
private final Map<String, CardRules> rulesByName;
|
||||
private final Map<String, ICardFace> facesByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
|
||||
private static Map<String, String> artPrefs = new HashMap<>();
|
||||
private static Map<String, String> artPrefs = Maps.newHashMap();
|
||||
|
||||
private final Map<String, String> alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
|
||||
private final Map<String, Integer> artIds = new HashMap<>();
|
||||
private final Map<String, Integer> artIds = Maps.newHashMap();
|
||||
|
||||
private final CardEdition.Collection editions;
|
||||
private List<String> filtered;
|
||||
|
||||
private Map<String, Boolean> nonLegendaryCreatureNames = Maps.newHashMap();
|
||||
|
||||
public enum CardArtPreference {
|
||||
LATEST_ART_ALL_EDITIONS(false, true),
|
||||
LATEST_ART_CORE_EXPANSIONS_REPRINT_ONLY(true, true),
|
||||
@@ -489,7 +493,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
return null;
|
||||
// 1. First off, try using all possible search parameters, to narrow down the actual cards looked for.
|
||||
String reqEditionCode = request.edition;
|
||||
PaperCard result = null;
|
||||
if (reqEditionCode != null && reqEditionCode.length() > 0) {
|
||||
// This get is robust even against expansion aliases (e.g. TE and TMP both valid for Tempest) -
|
||||
// MOST of the extensions have two short codes, 141 out of 221 (so far)
|
||||
@@ -843,6 +846,26 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
return facesByName.get(getName(name));
|
||||
}
|
||||
|
||||
public boolean isNonLegendaryCreatureName(final String name) {
|
||||
Boolean bool = nonLegendaryCreatureNames.get(name);
|
||||
if (bool != null) {
|
||||
return bool;
|
||||
}
|
||||
// check if the name is from a face
|
||||
// in general token creatures does not have this
|
||||
final ICardFace face = StaticData.instance().getCommonCards().getFaceByName(name);
|
||||
if (face == null) {
|
||||
nonLegendaryCreatureNames.put(name, false);
|
||||
return false;
|
||||
}
|
||||
// TODO add check if face is legal in the format of the game
|
||||
// name does need to be a non-legendary creature
|
||||
final CardType type = face.getType();
|
||||
bool = type.isCreature() && !type.isLegendary();
|
||||
nonLegendaryCreatureNames.put(name, bool);
|
||||
return bool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PaperCard> getAllCards() {
|
||||
return Collections.unmodifiableCollection(allCardsByName.values());
|
||||
|
||||
@@ -215,7 +215,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
}
|
||||
|
||||
public boolean canBeCommander() {
|
||||
if (mainPart.getOracleText().contains("can be your commander")) {
|
||||
if (mainPart.getOracleText().contains("can be your commander") || canBeBackground()) {
|
||||
return true;
|
||||
}
|
||||
CardType type = mainPart.getType();
|
||||
@@ -232,8 +232,15 @@ public final class CardRules implements ICardCharacteristics {
|
||||
}
|
||||
|
||||
public boolean canBePartnerCommander() {
|
||||
if (canBeBackground()) {
|
||||
return true;
|
||||
}
|
||||
return canBeCommander() && (hasKeyword("Partner") || !this.partnerWith.isEmpty() ||
|
||||
hasKeyword("Friends forever"));
|
||||
hasKeyword("Friends forever") || hasKeyword("Choose a Background"));
|
||||
}
|
||||
|
||||
public boolean canBeBackground() {
|
||||
return mainPart.getType().hasSubtype("Background");
|
||||
}
|
||||
|
||||
public boolean canBeOathbreaker() {
|
||||
|
||||
@@ -209,6 +209,16 @@ public final class CardRulesPredicates {
|
||||
};
|
||||
}
|
||||
|
||||
public static Predicate<CardRules> deckHasExactly(final DeckHints.Type type, final String has[]) {
|
||||
return new Predicate<CardRules>() {
|
||||
@Override
|
||||
public boolean apply(final CardRules card) {
|
||||
DeckHints deckHas = card.getAiHints().getDeckHas();
|
||||
return deckHas != null && deckHas.isValid() && deckHas.is(type, has);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Core type.
|
||||
*
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.item.PaperCard;
|
||||
import forge.util.PredicateString.StringOp;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
/**
|
||||
* DeckHints provides the ability for a Card to "want" another Card or type of
|
||||
@@ -73,12 +74,26 @@ public class DeckHints {
|
||||
return false;
|
||||
}
|
||||
for (Pair<Type, String> filter : filters) {
|
||||
if (filter.getLeft() == type && filter.getRight().equals(hint)) {
|
||||
if (filter.getLeft() == type && filter.getRight().contains(hint)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public boolean is(Type type, String hints[]) {
|
||||
if (filters == null) {
|
||||
return false;
|
||||
}
|
||||
for (String hint : hints) {
|
||||
for (Pair<Type, String> filter : filters) {
|
||||
if (filter.getLeft() == type && filter.getRight().equals(hint)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Map of Cards by Type from the given Iterable<PaperCard> that match this
|
||||
@@ -95,6 +110,10 @@ public class DeckHints {
|
||||
String param = pair.getRight();
|
||||
Iterable<PaperCard> cards = getCardsForFilter(cardList, type, param);
|
||||
if (cards != null) {
|
||||
// if a type is used more than once intersect respective matches
|
||||
if (ret.get(type) != null) {
|
||||
Iterables.retainAll(cards, new FCollection<>(ret.get(type)));
|
||||
}
|
||||
ret.put(type, cards);
|
||||
}
|
||||
}
|
||||
@@ -143,13 +162,16 @@ public class DeckHints {
|
||||
|
||||
private Iterable<PaperCard> getCardsForFilter(Iterable<PaperCard> cardList, Type type, String param) {
|
||||
List<PaperCard> cards = new ArrayList<>();
|
||||
|
||||
// this is case ABILITY, but other types can also use this when the implicit parsing would miss
|
||||
String[] abilities = param.split("\\|");
|
||||
for (String ability : abilities) {
|
||||
Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.deckHas(type, ability), PaperCard.FN_GET_RULES));
|
||||
}
|
||||
// bonus if a DeckHas can satisfy the type with multiple ones
|
||||
Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.deckHasExactly(type, abilities), PaperCard.FN_GET_RULES));
|
||||
|
||||
switch (type) {
|
||||
case ABILITY:
|
||||
String[] abilities = param.split("\\|");
|
||||
for (String ability : abilities) {
|
||||
Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.deckHas(Type.ABILITY, ability), PaperCard.FN_GET_RULES));
|
||||
}
|
||||
break;
|
||||
case COLOR:
|
||||
String[] colors = param.split("\\|");
|
||||
for (String color : colors) {
|
||||
@@ -187,6 +209,7 @@ public class DeckHints {
|
||||
}
|
||||
break;
|
||||
case NONE:
|
||||
case ABILITY: // already done above
|
||||
break;
|
||||
}
|
||||
return cards;
|
||||
|
||||
@@ -262,6 +262,9 @@ public enum DeckFormat {
|
||||
} else if (a.getRules().hasKeyword("Friends forever") &&
|
||||
b.getRules().hasKeyword("Friends forever")) {
|
||||
// Stranger Things Secret Lair gimmick partner commander
|
||||
} else if (a.getRules().hasKeyword("Choose a Background")
|
||||
&& b.getRules().canBeBackground()) {
|
||||
// commander with background
|
||||
} else {
|
||||
return "has an illegal commander partnership";
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ public enum DeckSection {
|
||||
@Override
|
||||
public Boolean apply(PaperCard card) {
|
||||
CardType t = card.getRules().getType();
|
||||
return card.getRules().canBeCommander() || t.isPlaneswalker();
|
||||
return card.getRules().canBeCommander() || t.isPlaneswalker() || card.getRules().canBeOathbreaker() || card.getRules().canBeSignatureSpell();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -422,7 +422,7 @@ public abstract class DeckGeneratorBase {
|
||||
}
|
||||
|
||||
public List<String> regexLandSearch(String pattern, Iterable<PaperCard> landCards){
|
||||
final List<String> dLands = new ArrayList<>();
|
||||
//final List<String> dLands = new ArrayList<>();
|
||||
Pattern p = Pattern.compile(pattern);
|
||||
for (PaperCard card:landCards){
|
||||
if (card.getRules().getAiHints().getRemAIDecks()) {
|
||||
|
||||
@@ -37,7 +37,7 @@ public class FatPack extends BoxedProduct {
|
||||
if (boosters <= 0) { return null; }
|
||||
|
||||
FatPack.Template d = new Template(edition);
|
||||
if (d == null) { return null; }
|
||||
if (d == null || null == StaticData.instance().getBoosters().get(d.getEdition())) { return null; }
|
||||
return new FatPack(edition.getName(), d, d.cntBoosters);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.50-SNAPSHOT</version>
|
||||
<version>1.6.50</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
@@ -4,11 +4,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
@@ -37,8 +35,6 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
protected Card hostCard;
|
||||
protected CardState cardState = null;
|
||||
|
||||
protected List<StaticLayerInterface> grantedByStatic = Lists.newArrayList();
|
||||
|
||||
/** The map params. */
|
||||
protected Map<String, String> originalMapParams = Maps.newHashMap(),
|
||||
mapParams = Maps.newHashMap();
|
||||
@@ -593,31 +589,6 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
return null;
|
||||
}
|
||||
|
||||
public Card getOriginalOrHost() {
|
||||
return ObjectUtils.defaultIfNull(getOriginalHost(), getHostCard());
|
||||
}
|
||||
|
||||
public List<StaticLayerInterface> getGrantedByStatic() {
|
||||
return grantedByStatic;
|
||||
}
|
||||
|
||||
public void setGrantedByStatic(List<StaticLayerInterface> list) {
|
||||
this.grantedByStatic = Lists.newArrayList(list);
|
||||
}
|
||||
|
||||
public void addGrantedByStatic(final StaticLayerInterface stAb) {
|
||||
grantedByStatic.add(stAb);
|
||||
}
|
||||
|
||||
// TODO currently Clone effects doesn't set Grantor like giving Abilities does
|
||||
// if it would, then this needs to be refactored anyway
|
||||
public Card getFirstGrantor() {
|
||||
if (grantedByStatic.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return grantedByStatic.get(0).getHostCard();
|
||||
}
|
||||
|
||||
public boolean isCopiedTrait() {
|
||||
if (this.getCardState() == null) {
|
||||
return false;
|
||||
@@ -679,8 +650,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
copy.mapParams = Maps.newHashMap(originalMapParams);
|
||||
copy.setSVars(sVars);
|
||||
copy.setCardState(cardState);
|
||||
copy.setGrantedByStatic(grantedByStatic);
|
||||
// don't use setHostCard to not trigger the not copied parts yet
|
||||
// dont use setHostCard to not trigger the not copied parts yet
|
||||
copy.hostCard = host;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,16 +10,21 @@ import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardState;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.mana.Mana;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityPredicates;
|
||||
import forge.game.spellability.TargetChoices;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Expressions;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class ForgeScript {
|
||||
|
||||
public static boolean cardStateHasProperty(CardState cardState, String property, Player sourceController,
|
||||
@@ -178,7 +183,7 @@ public class ForgeScript {
|
||||
} else if (property.equals("Modular")) {
|
||||
return sa.hasParam("Modular");
|
||||
} else if (property.equals("Equip")) {
|
||||
return sa.hasParam("Equip");
|
||||
return sa.isEquip();
|
||||
} else if (property.equals("Boast")) {
|
||||
return sa.isBoast();
|
||||
} else if (property.equals("Mutate")) {
|
||||
@@ -197,12 +202,39 @@ public class ForgeScript {
|
||||
return sa.hasParam("Nightbound");
|
||||
} else if (property.equals("paidPhyrexianMana")) {
|
||||
return sa.getSpendPhyrexianMana();
|
||||
} else if (property.startsWith("ManaSpent")) {
|
||||
String[] k = property.split(" ", 2);
|
||||
String comparator = k[1].substring(0, 2);
|
||||
int y = AbilityUtils.calculateAmount(sa.getHostCard(), k[1].substring(2), sa);
|
||||
return Expressions.compare(sa.getPayingMana().size(), comparator, y);
|
||||
} else if (property.startsWith("ManaFrom")) {
|
||||
final String fromWhat = property.substring(8);
|
||||
boolean found = false;
|
||||
for (Mana m : sa.getPayingMana()) {
|
||||
final Card manaSource = m.getSourceCard();
|
||||
if (manaSource != null) {
|
||||
if (manaSource.isValid(fromWhat, sourceController, source, spellAbility)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
} else if (property.equals("MayPlaySource")) {
|
||||
StaticAbility m = sa.getMayPlay();
|
||||
if (m == null) {
|
||||
return false;
|
||||
}
|
||||
return source.equals(m.getHostCard());
|
||||
} else if (property.startsWith("numTargets")) {
|
||||
Set<GameObject> targets = new HashSet<>();
|
||||
for (TargetChoices tc : sa.getAllTargetChoices()) {
|
||||
targets.addAll(tc);
|
||||
}
|
||||
String[] k = property.split(" ", 2);
|
||||
String comparator = k[1].substring(0, 2);
|
||||
int y = AbilityUtils.calculateAmount(sa.getHostCard(), k[1].substring(2), sa);
|
||||
return Expressions.compare(targets.size(), comparator, y);
|
||||
} else if (property.startsWith("IsTargeting")) {
|
||||
String[] k = property.split(" ", 2);
|
||||
boolean found = false;
|
||||
|
||||
@@ -117,9 +117,6 @@ public class Game {
|
||||
private CardCollection lastStateBattlefield = new CardCollection();
|
||||
private CardCollection lastStateGraveyard = new CardCollection();
|
||||
|
||||
private Map<Player, PlayerCollection> attackedThisTurn = Maps.newHashMap();
|
||||
private Map<Player, PlayerCollection> attackedLastTurn = Maps.newHashMap();
|
||||
|
||||
private Table<CounterType, Player, List<Pair<Card, Integer>>> countersAddedThisTurn = HashBasedTable.create();
|
||||
|
||||
private Map<Player, Card> topLibsCast = Maps.newHashMap();
|
||||
@@ -176,28 +173,6 @@ public class Game {
|
||||
this.monarchBeginTurn = monarchBeginTurn;
|
||||
}
|
||||
|
||||
public Map<Player, PlayerCollection> getPlayersAttackedThisTurn() {
|
||||
return attackedThisTurn;
|
||||
}
|
||||
|
||||
public Map<Player, PlayerCollection> getPlayersAttackedLastTurn() {
|
||||
return attackedLastTurn;
|
||||
}
|
||||
|
||||
public void addPlayerAttackedThisTurn(Player attacker, Player defender) {
|
||||
PlayerCollection atk = attackedThisTurn.get(attacker);
|
||||
if (atk == null) {
|
||||
attackedThisTurn.put(attacker, new PlayerCollection());
|
||||
}
|
||||
attackedThisTurn.get(attacker).add(defender);
|
||||
}
|
||||
|
||||
public void resetPlayersAttackedOnNextTurn() {
|
||||
attackedLastTurn.clear();
|
||||
attackedLastTurn.putAll(attackedThisTurn);
|
||||
attackedThisTurn.clear();
|
||||
}
|
||||
|
||||
public CardCollectionView getLastStateBattlefield() {
|
||||
return lastStateBattlefield;
|
||||
}
|
||||
@@ -1093,8 +1068,6 @@ public class Game {
|
||||
|
||||
public void onCleanupPhase() {
|
||||
clearCounterAddedThisTurn();
|
||||
// Reset the attackers this turn/last turn
|
||||
resetPlayersAttackedOnNextTurn();
|
||||
// some cards need this info updated even after a player lost, so don't skip them
|
||||
for (Player player : getRegisteredPlayers()) {
|
||||
player.onCleanupPhase();
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package forge.game;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
@@ -31,12 +30,12 @@ import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import forge.GameCommand;
|
||||
@@ -162,13 +161,13 @@ public class GameAction {
|
||||
// need to check before it enters
|
||||
if (c.isAura() && !c.isAttachedToEntity() && toBattlefield && (zoneFrom == null || !zoneFrom.is(ZoneType.Stack))) {
|
||||
boolean found = false;
|
||||
if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(c))) {
|
||||
if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(c, null))) {
|
||||
found = true;
|
||||
}
|
||||
else if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(c))) {
|
||||
else if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(c, null))) {
|
||||
found = true;
|
||||
}
|
||||
else if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(c))) {
|
||||
else if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(c, null))) {
|
||||
found = true;
|
||||
}
|
||||
if (!found) {
|
||||
@@ -275,7 +274,12 @@ public class GameAction {
|
||||
lastKnownInfo = CardUtil.getLKICopy(c);
|
||||
}
|
||||
|
||||
copied = CardFactory.copyCard(c, false);
|
||||
// CR 707.12 casting of a card copy, don't copy it again
|
||||
if (zoneTo.is(ZoneType.Stack) && c.isRealToken()) {
|
||||
copied = c;
|
||||
} else {
|
||||
copied = CardFactory.copyCard(c, false);
|
||||
}
|
||||
|
||||
if (zoneTo.is(ZoneType.Stack)) {
|
||||
// when moving to stack, copy changed card information
|
||||
@@ -398,13 +402,13 @@ public class GameAction {
|
||||
if (copied.isAura() && !copied.isAttachedToEntity() && toBattlefield) {
|
||||
if (zoneFrom != null && zoneFrom.is(ZoneType.Stack) && game.getStack().isResolving(c)) {
|
||||
boolean found = false;
|
||||
if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(copied))) {
|
||||
if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(copied, null))) {
|
||||
found = true;
|
||||
}
|
||||
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(copied))) {
|
||||
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(copied, null))) {
|
||||
found = true;
|
||||
}
|
||||
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(copied))) {
|
||||
if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(copied, null))) {
|
||||
found = true;
|
||||
}
|
||||
if (!found) {
|
||||
@@ -577,6 +581,8 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
table.replaceCounterEffect(game, null, true);
|
||||
|
||||
// Need to apply any static effects to produce correct triggers
|
||||
checkStaticAbilities();
|
||||
|
||||
@@ -592,8 +598,6 @@ public class GameAction {
|
||||
}
|
||||
game.getTriggerHandler().registerActiveTrigger(copied, false);
|
||||
|
||||
table.replaceCounterEffect(game, null, true);
|
||||
|
||||
// play the change zone sound
|
||||
game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo));
|
||||
|
||||
@@ -1515,7 +1519,7 @@ public class GameAction {
|
||||
|
||||
if (c.isAttachedToEntity()) {
|
||||
final GameEntity ge = c.getEntityAttachedTo();
|
||||
if (!ge.canBeAttached(c, true)) {
|
||||
if (!ge.canBeAttached(c, null, true)) {
|
||||
unAttachList.add(c);
|
||||
checkAgain = true;
|
||||
}
|
||||
@@ -1692,39 +1696,71 @@ public class GameAction {
|
||||
}
|
||||
|
||||
private boolean handleLegendRule(Player p, CardCollection noRegCreats) {
|
||||
final List<Card> a = CardLists.getType(p.getCardsIn(ZoneType.Battlefield), "Legendary");
|
||||
if (a.isEmpty() || game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
|
||||
return false;
|
||||
}
|
||||
boolean recheck = false;
|
||||
// TODO legend rule exception into static ability
|
||||
List<Card> yamazaki = CardLists.getKeyword(a, "Legend rule doesn't apply to CARDNAME.");
|
||||
a.removeAll(yamazaki);
|
||||
final List<Card> a = Lists.newArrayList();
|
||||
|
||||
|
||||
Multimap<String, Card> uniqueLegends = ArrayListMultimap.create();
|
||||
for (Card c : a) {
|
||||
if (!c.isFaceDown()) {
|
||||
uniqueLegends.put(c.getName(), c);
|
||||
// check for ignore legend rule
|
||||
for (Card c : CardLists.getType(p.getCardsIn(ZoneType.Battlefield), "Legendary")) {
|
||||
if (!c.ignoreLegendRule()) {
|
||||
a.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO handle Spy Kit
|
||||
if (a.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
boolean recheck = false;
|
||||
|
||||
// Corner Case 1: Legendary with non legendary creature names
|
||||
CardCollection nonLegendaryNames = new CardCollection(Iterables.filter(a, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card input) {
|
||||
return input.hasNonLegendaryCreatureNames();
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
Multimap<String, Card> uniqueLegends = Multimaps.index(a, CardPredicates.Accessors.fnGetNetName);
|
||||
CardCollection removed = new CardCollection();
|
||||
|
||||
for (String name : uniqueLegends.keySet()) {
|
||||
Collection<Card> cc = uniqueLegends.get(name);
|
||||
// skip the ones with empty names
|
||||
if (name.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
CardCollection cc = new CardCollection(uniqueLegends.get(name));
|
||||
// check if it is a non legendary creature name
|
||||
// if yes, then add the other legendary with Spy Kit too
|
||||
if (!name.isEmpty() && StaticData.instance().getCommonCards().isNonLegendaryCreatureName(name)) {
|
||||
cc.addAll(nonLegendaryNames);
|
||||
}
|
||||
if (cc.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
recheck = true;
|
||||
|
||||
Card toKeep = p.getController().chooseSingleEntityForEffect(new CardCollection(cc), new SpellAbility.EmptySa(ApiType.InternalLegendaryRule, new Card(-1, game), p),
|
||||
Card toKeep = p.getController().chooseSingleEntityForEffect(cc, new SpellAbility.EmptySa(ApiType.InternalLegendaryRule, new Card(-1, game), p),
|
||||
"You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)", null);
|
||||
cc.remove(toKeep);
|
||||
noRegCreats.addAll(cc);
|
||||
removed.addAll(cc);
|
||||
}
|
||||
|
||||
// Corner Case 2: with all non legendary creature names
|
||||
CardCollection emptyNameAllNonLegendary = new CardCollection(nonLegendaryNames);
|
||||
// remove the ones that got already removed by other legend rule above
|
||||
emptyNameAllNonLegendary.removeAll(removed);
|
||||
if (emptyNameAllNonLegendary.size() > 1) {
|
||||
|
||||
recheck = true;
|
||||
|
||||
Card toKeep = p.getController().chooseSingleEntityForEffect(emptyNameAllNonLegendary, new SpellAbility.EmptySa(ApiType.InternalLegendaryRule, new Card(-1, game), p),
|
||||
"You have multiple legendary permanents with non legendary creature names in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)", null);
|
||||
emptyNameAllNonLegendary.remove(toKeep);
|
||||
removed.addAll(emptyNameAllNonLegendary);
|
||||
|
||||
}
|
||||
noRegCreats.addAll(removed);
|
||||
|
||||
return recheck;
|
||||
}
|
||||
|
||||
@@ -1775,9 +1811,8 @@ public class GameAction {
|
||||
}
|
||||
|
||||
// Replacement effects
|
||||
final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromCard(c);
|
||||
final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(c);
|
||||
repRunParams.put(AbilityKey.Source, sa);
|
||||
repRunParams.put(AbilityKey.Affected, c);
|
||||
repRunParams.put(AbilityKey.Regeneration, regenerate);
|
||||
if (params != null) {
|
||||
repRunParams.putAll(params);
|
||||
@@ -2382,7 +2417,7 @@ public class GameAction {
|
||||
final Player pa = p.getController().chooseSingleEntityForEffect(players, aura,
|
||||
Localizer.getInstance().getMessage("lblSelectAPlayerAttachSourceTo", CardTranslation.getTranslatedName(source.getName())), null);
|
||||
if (pa != null) {
|
||||
source.attachToEntity(pa);
|
||||
source.attachToEntity(pa, null);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
@@ -2409,7 +2444,7 @@ public class GameAction {
|
||||
final Card o = p.getController().chooseSingleEntityForEffect(list, aura,
|
||||
Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(source.getName())), null);
|
||||
if (o != null) {
|
||||
source.attachToEntity(game.getCardState(o), true);
|
||||
source.attachToEntity(game.getCardState(o), null, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,7 +341,7 @@ public final class GameActionUtil {
|
||||
alternatives.add(newSA);
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("Equip") && activator.hasKeyword("You may pay 0 rather than pay equip costs.")) {
|
||||
if (sa.isEquip() && activator.hasKeyword("You may pay 0 rather than pay equip costs.")) {
|
||||
for (final KeywordInterface inst : source.getKeywords()) {
|
||||
// need to find the correct Keyword from which this Ability is from
|
||||
if (!inst.getAbilities().contains(sa)) {
|
||||
@@ -487,27 +487,17 @@ public final class GameActionUtil {
|
||||
final String keyword = inst.getOriginal();
|
||||
if (keyword.startsWith("AlternateAdditionalCost")) {
|
||||
final List<SpellAbility> newAbilities = Lists.newArrayList();
|
||||
String[] costs = TextUtil.split(keyword, ':');
|
||||
|
||||
final SpellAbility newSA = sa.copy();
|
||||
newSA.setBasicSpell(false);
|
||||
for (String s : keyword.split(":", 2)[1].split(":")) {
|
||||
final SpellAbility newSA = sa.copy();
|
||||
newSA.setBasicSpell(false);
|
||||
|
||||
final Cost cost1 = new Cost(costs[1], false);
|
||||
newSA.setDescription(sa.getDescription() + " (Additional cost " + cost1.toSimpleString() + ")");
|
||||
newSA.setPayCosts(cost1.add(sa.getPayCosts()));
|
||||
if (newSA.canPlay()) {
|
||||
newAbilities.add(newSA);
|
||||
}
|
||||
|
||||
//second option
|
||||
final SpellAbility newSA2 = sa.copy();
|
||||
newSA2.setBasicSpell(false);
|
||||
|
||||
final Cost cost2 = new Cost(costs[2], false);
|
||||
newSA2.setDescription(sa.getDescription() + " (Additional cost " + cost2.toSimpleString() + ")");
|
||||
newSA2.setPayCosts(cost2.add(sa.getPayCosts()));
|
||||
if (newSA2.canPlay()) {
|
||||
newAbilities.add(newSA2);
|
||||
final Cost cost = new Cost(s, false);
|
||||
newSA.setDescription(sa.getDescription() + " (Additional cost: " + cost.toSimpleString() + ")");
|
||||
newSA.setPayCosts(cost.add(sa.getPayCosts()));
|
||||
if (newSA.canPlay()) {
|
||||
newAbilities.add(newSA);
|
||||
}
|
||||
}
|
||||
|
||||
abilities.clear();
|
||||
|
||||
@@ -212,10 +212,10 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canBeAttached(final Card attach) {
|
||||
return canBeAttached(attach, false);
|
||||
public boolean canBeAttached(final Card attach, SpellAbility sa) {
|
||||
return canBeAttached(attach, sa, false);
|
||||
}
|
||||
public boolean canBeAttached(final Card attach, boolean checkSBA) {
|
||||
public boolean canBeAttached(final Card attach, SpellAbility sa, boolean checkSBA) {
|
||||
// master mode
|
||||
if (!attach.isAttachment() || (attach.isCreature() && !attach.hasKeyword(Keyword.RECONFIGURE))
|
||||
|| equals(attach)) {
|
||||
@@ -226,7 +226,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
if (attach.isAura() && !canBeEnchantedBy(attach)) {
|
||||
return false;
|
||||
}
|
||||
if (attach.isEquipment() && !canBeEquippedBy(attach)) {
|
||||
if (attach.isEquipment() && !canBeEquippedBy(attach, sa)) {
|
||||
return false;
|
||||
}
|
||||
if (attach.isFortification() && !canBeFortifiedBy(attach)) {
|
||||
@@ -242,7 +242,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean canBeEquippedBy(final Card aura) {
|
||||
protected boolean canBeEquippedBy(final Card aura, SpellAbility sa) {
|
||||
/**
|
||||
* Equip only to Creatures which are cards
|
||||
*/
|
||||
|
||||
@@ -28,7 +28,6 @@ public enum GlobalRuleChange {
|
||||
noCreatureETBTriggers ("Creatures entering the battlefield don't cause abilities to trigger."),
|
||||
noCreatureDyingTriggers ("Creatures dying don't cause abilities to trigger."),
|
||||
noNight ("It can't become night."),
|
||||
noLegendRule ("The legend rule doesn't apply."),
|
||||
/* onlyOneAttackerATurn ("No more than one creature can attack each turn."), */
|
||||
onlyOneAttackerACombat ("No more than one creature can attack each combat."),
|
||||
onlyOneBlocker ("No more than one creature can block each combat."),
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package forge.game;
|
||||
|
||||
import forge.game.card.Card;
|
||||
|
||||
public interface StaticLayerInterface {
|
||||
|
||||
public Card getHostCard();
|
||||
}
|
||||
@@ -50,7 +50,7 @@ import io.sentry.Sentry;
|
||||
*/
|
||||
public final class AbilityFactory {
|
||||
|
||||
static final List<String> additionalAbilityKeys = Lists.newArrayList(
|
||||
public static final List<String> additionalAbilityKeys = Lists.newArrayList(
|
||||
"WinSubAbility", "OtherwiseSubAbility", // Clash
|
||||
"BidSubAbility", // BidLifeEffect
|
||||
"ChooseNumberSubAbility", "Lowest", "Highest", "NotLowest", // ChooseNumber
|
||||
@@ -62,7 +62,8 @@ public final class AbilityFactory {
|
||||
"FallbackAbility", // Complex Unless costs which can be unpayable
|
||||
"ChooseSubAbility", // Can choose a player via ChoosePlayer
|
||||
"CantChooseSubAbility", // Can't choose a player via ChoosePlayer
|
||||
"AnimateSubAbility" // For ChangeZone Effects to Animate before ETB
|
||||
"AnimateSubAbility", // For ChangeZone Effects to Animate before ETB
|
||||
"ReturnAbility" // for Delayed Trigger on Magpie
|
||||
);
|
||||
|
||||
public enum AbilityRecordType {
|
||||
@@ -79,7 +80,7 @@ public final class AbilityFactory {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public SpellAbility buildSpellAbility(ApiType api, Card hostCard, Cost abCost, TargetRestrictions abTgt, Map<String, String> mapParams ) {
|
||||
public SpellAbility buildSpellAbility(ApiType api, Card hostCard, Cost abCost, TargetRestrictions abTgt, Map<String, String> mapParams) {
|
||||
switch(this) {
|
||||
case Ability: return new AbilityApiBased(api, hostCard, abCost, abTgt, mapParams);
|
||||
case Spell: return new SpellApiBased(api, hostCard, abCost, abTgt, mapParams);
|
||||
@@ -333,7 +334,7 @@ public final class AbilityFactory {
|
||||
String tgtWhat = mapParams.get("ValidTgts");
|
||||
final String[] commonStuff = new String[] {
|
||||
//list of common one word non-core type ValidTgts that should be lowercase in the target prompt
|
||||
"Player", "Opponent"
|
||||
"Player", "Opponent", "Card"
|
||||
};
|
||||
if (Arrays.asList(commonStuff).contains(tgtWhat) || CardType.CoreType.isValidEnum(tgtWhat)) {
|
||||
tgtWhat = tgtWhat.toLowerCase();
|
||||
@@ -364,9 +365,6 @@ public final class AbilityFactory {
|
||||
abTgt.setSAValidTargeting(mapParams.get("TargetValidTargeting"));
|
||||
}
|
||||
|
||||
if (mapParams.containsKey("TargetsSingleTarget")) {
|
||||
abTgt.setSingleTarget(true);
|
||||
}
|
||||
if (mapParams.containsKey("TargetUnique")) {
|
||||
abTgt.setUniqueTargets(true);
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ public enum AbilityKey {
|
||||
SourceSA("SourceSA"),
|
||||
SpellAbility("SpellAbility"),
|
||||
SpellAbilityStackInstance("SpellAbilityStackInstance"),
|
||||
SpellAbilityTarget("SpellAbilityTarget"),
|
||||
SpellAbilityTargetingCards("SpellAbilityTargetingCards"),
|
||||
StackInstance("StackInstance"),
|
||||
StackSa("StackSa"),
|
||||
|
||||
@@ -105,9 +105,9 @@ public class AbilityUtils {
|
||||
}
|
||||
else if (defined.equals("OriginalHost")) {
|
||||
if (sa instanceof SpellAbility) {
|
||||
c = ((SpellAbility)sa).getRootAbility().getFirstGrantor();
|
||||
c = ((SpellAbility)sa).getRootAbility().getOriginalHost();
|
||||
} else {
|
||||
c = sa.getFirstGrantor();
|
||||
c = sa.getOriginalHost();
|
||||
}
|
||||
}
|
||||
else if (defined.equals("EffectSource")) {
|
||||
@@ -539,7 +539,7 @@ public class AbilityUtils {
|
||||
val = 0;
|
||||
}
|
||||
} else if (calcX[0].equals("OriginalHost")) {
|
||||
val = xCount(ability.getFirstGrantor(), calcX[1], ability);
|
||||
val = xCount(ability.getOriginalHost(), calcX[1], ability);
|
||||
} else if (calcX[0].startsWith("Remembered")) {
|
||||
// Add whole Remembered list to handlePaid
|
||||
final CardCollection list = new CardCollection();
|
||||
@@ -742,6 +742,9 @@ public class AbilityUtils {
|
||||
else if (calcX[0].startsWith("Revealed")) {
|
||||
list = sa.getRootAbility().getPaidList("Revealed");
|
||||
}
|
||||
else if (calcX[0].startsWith("Returned")) {
|
||||
list = sa.getRootAbility().getPaidList("Returned");
|
||||
}
|
||||
else if (calcX[0].startsWith("Targeted")) {
|
||||
list = sa.findTargetedCards();
|
||||
}
|
||||
@@ -966,7 +969,7 @@ public class AbilityUtils {
|
||||
|
||||
final Player player = sa instanceof SpellAbility ? ((SpellAbility)sa).getActivatingPlayer() : card.getController();
|
||||
|
||||
if (defined.equals("Self") || defined.equals("ThisTargetedCard") || getPaidCards(sa, defined) != null) {
|
||||
if (defined.equals("Self") || defined.equals("ThisTargetedCard") || defined.startsWith("Valid") || getPaidCards(sa, defined) != null) {
|
||||
// do nothing, Self is for Cards, not Players
|
||||
} else if (defined.equals("TargetedOrController")) {
|
||||
players.addAll(getDefinedPlayers(card, "Targeted", sa));
|
||||
@@ -1035,6 +1038,11 @@ public class AbilityUtils {
|
||||
if (root != null) {
|
||||
addPlayer(Lists.newArrayList(root), defined, players);
|
||||
}
|
||||
} else if (defined.startsWith("OriginalHost")) {
|
||||
Card originalHost = sa.getOriginalHost();
|
||||
if (originalHost != null) {
|
||||
addPlayer(Lists.newArrayList(originalHost), defined, players);
|
||||
}
|
||||
}
|
||||
else if (defined.startsWith("DelayTriggerRemembered") && sa instanceof SpellAbility) {
|
||||
SpellAbility root = ((SpellAbility)sa).getRootAbility();
|
||||
@@ -1052,17 +1060,16 @@ public class AbilityUtils {
|
||||
final SpellAbility root = ((SpellAbility)sa).getRootAbility();
|
||||
Object o = null;
|
||||
if (defParsed.endsWith("Controller")) {
|
||||
String triggeringType = defParsed.substring(9);
|
||||
triggeringType = triggeringType.substring(0, triggeringType.length() - 10);
|
||||
final boolean orCont = defParsed.endsWith("OrController");
|
||||
String triggeringType = defParsed.substring(9, defParsed.length() - (orCont ? 12 : 10));
|
||||
final Object c = root.getTriggeringObject(AbilityKey.fromString(triggeringType));
|
||||
if (c instanceof Card) {
|
||||
if (orCont && c instanceof Player) {
|
||||
o = c;
|
||||
} else if (c instanceof Card) {
|
||||
o = ((Card) c).getController();
|
||||
}
|
||||
if (c instanceof SpellAbility) {
|
||||
} else if (c instanceof SpellAbility) {
|
||||
o = ((SpellAbility) c).getActivatingPlayer();
|
||||
}
|
||||
// For merged permanent
|
||||
if (c instanceof CardCollection) {
|
||||
} else if (c instanceof CardCollection) { // For merged permanent
|
||||
o = ((CardCollection) c).get(0).getController();
|
||||
}
|
||||
}
|
||||
@@ -1594,42 +1601,39 @@ public class AbilityUtils {
|
||||
|
||||
if (sa.hasParam("RememberCostCards") && !sa.getPaidHash().isEmpty()) {
|
||||
List <Card> noList = Lists.newArrayList();
|
||||
Map<String, CardCollection> paidLists = sa.getPaidHash();
|
||||
if (sa.hasParam("RememberCostExcept")) {
|
||||
noList.addAll(AbilityUtils.getDefinedCards(host, sa.getParam("RememberCostExcept"), sa));
|
||||
}
|
||||
if (sa.getParam("Cost").contains("Exile")) {
|
||||
if (paidLists.containsKey("Exiled")) {
|
||||
final CardCollection paidListExiled = sa.getPaidList("Exiled");
|
||||
for (final Card exiledAsCost : paidListExiled) {
|
||||
if (!noList.contains(exiledAsCost)) {
|
||||
host.addRemembered(exiledAsCost);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (sa.getParam("Cost").contains("Sac")) {
|
||||
} else if (paidLists.containsKey("Sacrificed")) {
|
||||
final CardCollection paidListSacrificed = sa.getPaidList("Sacrificed");
|
||||
for (final Card sacrificedAsCost : paidListSacrificed) {
|
||||
if (!noList.contains(sacrificedAsCost)) {
|
||||
host.addRemembered(sacrificedAsCost);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (sa.getParam("Cost").contains("tapXType")) {
|
||||
} else if (paidLists.containsKey("Tapped")) {
|
||||
final CardCollection paidListTapped = sa.getPaidList("Tapped");
|
||||
for (final Card tappedAsCost : paidListTapped) {
|
||||
if (!noList.contains(tappedAsCost)) {
|
||||
host.addRemembered(tappedAsCost);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (sa.getParam("Cost").contains("Unattach")) {
|
||||
} else if (paidLists.containsKey("Unattached")) {
|
||||
final CardCollection paidListUnattached = sa.getPaidList("Unattached");
|
||||
for (final Card unattachedAsCost : paidListUnattached) {
|
||||
if (!noList.contains(unattachedAsCost)) {
|
||||
host.addRemembered(unattachedAsCost);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (sa.getParam("Cost").contains("Discard")) {
|
||||
} else if (paidLists.containsKey("Discarded")) {
|
||||
final CardCollection paidListDiscarded = sa.getPaidList("Discarded");
|
||||
for (final Card discardedAsCost : paidListDiscarded) {
|
||||
if (!noList.contains(discardedAsCost)) {
|
||||
@@ -1846,9 +1850,6 @@ public class AbilityUtils {
|
||||
}
|
||||
}
|
||||
list = CardLists.getValidCards(list, k[1], sa.getActivatingPlayer(), c, sa);
|
||||
if (k[0].contains("TotalToughness")) {
|
||||
return doXMath(Aggregates.sum(list, CardPredicates.Accessors.fnGetNetToughness), expr, c, ctb);
|
||||
}
|
||||
return doXMath(list.size(), expr, c, ctb);
|
||||
}
|
||||
|
||||
@@ -2345,9 +2346,6 @@ public class AbilityUtils {
|
||||
if (sq[0].equals("BloodthirstAmount")) {
|
||||
return doXMath(player.getBloodthirstAmount(), expr, c, ctb);
|
||||
}
|
||||
if (sq[0].equals("YourLandsPlayed")) {
|
||||
return doXMath(player.getLandsPlayedThisTurn(), expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].startsWith("YourCounters")) {
|
||||
// "YourCountersExperience" or "YourCountersPoison"
|
||||
@@ -2411,16 +2409,6 @@ public class AbilityUtils {
|
||||
return doXMath(cmc, expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].startsWith("ColorsCtrl")) {
|
||||
final String restriction = l[0].substring(11);
|
||||
final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
|
||||
byte n = 0;
|
||||
for (final Card card : list) {
|
||||
n |= card.getColor().getColor();
|
||||
}
|
||||
return doXMath(ColorSet.fromMask(n).countColors(), expr, c, ctb);
|
||||
}
|
||||
|
||||
// Count$AttackersDeclared
|
||||
if (sq[0].startsWith("AttackersDeclared")) {
|
||||
List<Card> attackers = player.getCreaturesAttackedThisTurn();
|
||||
@@ -2683,20 +2671,6 @@ public class AbilityUtils {
|
||||
return MyRandom.getRandom().nextInt(1+max-min) + min;
|
||||
}
|
||||
|
||||
// Count$TotalCounters.<counterType>_<valid>
|
||||
if (sq[0].startsWith("TotalCounters")) {
|
||||
final String[] restrictions = l[0].split("_");
|
||||
final CounterType cType = CounterType.getType(restrictions[1]);
|
||||
final String[] validFilter = restrictions[2].split(",");
|
||||
CardCollectionView validCards = game.getCardsIn(ZoneType.Battlefield);
|
||||
validCards = CardLists.getValidCards(validCards, validFilter, player, c, ctb);
|
||||
int cCount = 0;
|
||||
for (final Card card : validCards) {
|
||||
cCount += card.getCounters(cType);
|
||||
}
|
||||
return doXMath(cCount, expr, c, ctb);
|
||||
}
|
||||
|
||||
// Count$ThisTurnCast <Valid>
|
||||
// Count$LastTurnCast <Valid>
|
||||
if (sq[0].startsWith("ThisTurnCast") || sq[0].startsWith("LastTurnCast")) {
|
||||
@@ -2751,13 +2725,27 @@ public class AbilityUtils {
|
||||
String[] paidparts = l[0].split("\\$", 2);
|
||||
String[] lparts = paidparts[0].split(" ", 2);
|
||||
|
||||
final CardCollectionView cardsInZones;
|
||||
CardCollectionView cardsInZones = null;
|
||||
if (lparts[0].contains("All")) {
|
||||
cardsInZones = game.getCardsInGame();
|
||||
} else {
|
||||
cardsInZones = lparts[0].length() > 5
|
||||
? game.getCardsIn(ZoneType.listValueOf(lparts[0].substring(5)))
|
||||
: game.getCardsIn(ZoneType.Battlefield);
|
||||
final List<ZoneType> zones = ZoneType.listValueOf(lparts[0].length() > 5 ? lparts[0].substring(5) : "Battlefield");
|
||||
boolean usedLastState = false;
|
||||
if (ctb instanceof SpellAbility && zones.size() == 1) {
|
||||
SpellAbility sa = (SpellAbility) ctb;
|
||||
if (sa.isReplacementAbility()) {
|
||||
if (zones.get(0).equals(ZoneType.Battlefield)) {
|
||||
cardsInZones = sa.getLastStateBattlefield();
|
||||
usedLastState = true;
|
||||
} else if (zones.get(0).equals(ZoneType.Graveyard)) {
|
||||
cardsInZones = sa.getLastStateGraveyard();
|
||||
usedLastState = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!usedLastState) {
|
||||
cardsInZones = game.getCardsIn(zones);
|
||||
}
|
||||
}
|
||||
|
||||
int cnt;
|
||||
@@ -2770,7 +2758,7 @@ public class AbilityUtils {
|
||||
}
|
||||
|
||||
if (sq[0].startsWith("MostCardName")) {
|
||||
String[] lparts = l[0].split(" ", 2);
|
||||
String[] lparts = l[0].split(" ", 2);
|
||||
final String[] rest = lparts[1].split(",");
|
||||
|
||||
final CardCollectionView cardsInZones = lparts[0].length() > 12
|
||||
@@ -2843,16 +2831,23 @@ public class AbilityUtils {
|
||||
return Aggregates.max(list, CardPredicates.Accessors.fnGetCmc);
|
||||
}
|
||||
if (sq[0].startsWith("DifferentPower_")) {
|
||||
final List<Integer> powers = Lists.newArrayList();
|
||||
final String restriction = l[0].substring(15);
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
|
||||
final Iterable<Card> powers = Aggregates.uniqueByLast(list, CardPredicates.Accessors.fnGetNetPower);
|
||||
return doXMath(Iterables.size(powers), expr, c, ctb);
|
||||
}
|
||||
if (sq[0].startsWith("DifferentCounterKinds_")) {
|
||||
final List<CounterType> kinds = Lists.newArrayList();
|
||||
final String rest = l[0].substring(22);
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb);
|
||||
for (final Card card : list) {
|
||||
Integer pow = card.getNetPower();
|
||||
if (!powers.contains(pow)) {
|
||||
powers.add(pow);
|
||||
for (final Map.Entry<CounterType, Integer> map : card.getCounters().entrySet()) {
|
||||
if (!kinds.contains(map.getKey())) {
|
||||
kinds.add(map.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
return doXMath(powers.size(), expr, c, ctb);
|
||||
return doXMath(kinds.size(), expr, c, ctb);
|
||||
}
|
||||
|
||||
// Complex counting methods
|
||||
@@ -3029,6 +3024,10 @@ public class AbilityUtils {
|
||||
}
|
||||
|
||||
private static void addPlayer(Iterable<Object> objects, final String def, FCollection<Player> players) {
|
||||
addPlayer(objects, def, players, false);
|
||||
}
|
||||
|
||||
private static void addPlayer(Iterable<Object> objects, final String def, FCollection<Player> players, boolean skipRemembered) {
|
||||
for (Object o : objects) {
|
||||
if (o instanceof Player) {
|
||||
final Player p = (Player) o;
|
||||
@@ -3043,6 +3042,9 @@ public class AbilityUtils {
|
||||
players.add(c.getController());
|
||||
} else if (def.endsWith("Owner")) {
|
||||
players.add(c.getOwner());
|
||||
} else if (def.endsWith("Remembered") && !skipRemembered) {
|
||||
//fixme recursive call to skip so it will not cause StackOverflow, ie Riveteers Overlook
|
||||
addPlayer(c.getRemembered(), def, players, true);
|
||||
}
|
||||
} else if (o instanceof SpellAbility) {
|
||||
final SpellAbility c = (SpellAbility) o;
|
||||
@@ -3300,6 +3302,21 @@ public class AbilityUtils {
|
||||
return doXMath(totPlayer, m, source, ctb);
|
||||
}
|
||||
|
||||
if (l[0].startsWith("Condition")) {
|
||||
int totPlayer = 0;
|
||||
String[] parts = l[0].split(" ", 2);
|
||||
boolean def = parts[0].equals("Condition");
|
||||
String comparator = !def ? parts[0].substring(9, 11) : "GE";
|
||||
int y = !def ? calculateAmount(source, parts[0].substring(11), ctb) : 1;
|
||||
for (Player p : players) {
|
||||
int x = playerXProperty(p, parts[1], source, ctb);
|
||||
if (Expressions.compare(x, comparator, y)) {
|
||||
totPlayer++;
|
||||
}
|
||||
}
|
||||
return doXMath(totPlayer, m, source, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].contains("DamageThisTurn")) {
|
||||
int totDmg = 0;
|
||||
for (Player p : players) {
|
||||
@@ -3347,6 +3364,18 @@ public class AbilityUtils {
|
||||
return doXMath(cardsonbattlefield.size(), m, source, ctb);
|
||||
}
|
||||
|
||||
if (l[0].startsWith("ThisTurnEntered")) {
|
||||
final String[] workingCopy = l[0].split("_");
|
||||
|
||||
ZoneType destination = ZoneType.smartValueOf(workingCopy[1]);
|
||||
final boolean hasFrom = workingCopy[2].equals("from");
|
||||
ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null;
|
||||
String validFilter = workingCopy[hasFrom ? 4 : 2] ;
|
||||
|
||||
final List<Card> res = CardUtil.getThisTurnEntered(destination, origin, validFilter, source, ctb, player);
|
||||
return doXMath(res.size(), m, source, ctb);
|
||||
}
|
||||
|
||||
final String[] sq = l[0].split("\\.");
|
||||
final String value = sq[0];
|
||||
|
||||
@@ -3436,10 +3465,6 @@ public class AbilityUtils {
|
||||
return doXMath(player.getNumDiscardedThisTurn(), m, source, ctb);
|
||||
}
|
||||
|
||||
if (value.contains("TokensCreatedThisTurn")) {
|
||||
return doXMath(player.getNumTokensCreatedThisTurn(), m, source, ctb);
|
||||
}
|
||||
|
||||
if (value.contains("AttackersDeclared")) {
|
||||
return doXMath(player.getCreaturesAttackedThisTurn().size(), m, source, ctb);
|
||||
}
|
||||
@@ -3453,10 +3478,14 @@ public class AbilityUtils {
|
||||
}
|
||||
|
||||
if (value.equals("OpponentsAttackedThisTurn")) {
|
||||
final PlayerCollection opps = game.getPlayersAttackedThisTurn().get(player);
|
||||
final List<Player> opps = player.getAttackedPlayersMyTurn();
|
||||
return doXMath(opps == null ? 0 : opps.size(), m, source, ctb);
|
||||
}
|
||||
|
||||
if (value.equals("OpponentsAttackedThisCombat")) {
|
||||
return doXMath(game.getCombat().getAttackedOpponents(player).size(), m, source, ctb);
|
||||
}
|
||||
|
||||
if (value.equals("DungeonsCompleted")) {
|
||||
return doXMath(player.getCompletedDungeons().size(), m, source, ctb);
|
||||
}
|
||||
|
||||
@@ -60,15 +60,6 @@ public abstract class SpellAbilityEffect {
|
||||
return sa.getDescription();
|
||||
}
|
||||
|
||||
protected static final void resolveSubAbility(final SpellAbility sa) {
|
||||
// if mana production has any type of SubAbility, undoable=false
|
||||
final AbilitySub abSub = sa.getSubAbility();
|
||||
if (abSub != null) {
|
||||
sa.setUndoable(false);
|
||||
AbilityUtils.resolve(abSub);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this effect description with needed prelude and epilogue.
|
||||
* @param params
|
||||
@@ -580,7 +571,7 @@ public abstract class SpellAbilityEffect {
|
||||
// important to update defenders here, maybe some PW got removed
|
||||
combat.initConstraints();
|
||||
if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
|
||||
PlayerCollection defendingPlayers = AbilityUtils.getDefinedPlayers(host, attacking, sa);
|
||||
PlayerCollection defendingPlayers = AbilityUtils.getDefinedPlayers(sa.hasParam("ForEach") ? c : host, attacking, sa);
|
||||
defs = new FCollection<>();
|
||||
for (Player p : defendingPlayers) {
|
||||
defs.addAll(combat.getDefendersControlledBy(p));
|
||||
@@ -649,9 +640,15 @@ public abstract class SpellAbilityEffect {
|
||||
return combatChanged;
|
||||
}
|
||||
|
||||
protected static GameCommand untilHostLeavesPlayCommand(final CardZoneTable triggerList, final Card hostCard) {
|
||||
protected static GameCommand untilHostLeavesPlayCommand(final CardZoneTable triggerList, final SpellAbility sa) {
|
||||
final Card hostCard = sa.getHostCard();
|
||||
final Game game = hostCard.getGame();
|
||||
hostCard.addUntilLeavesBattlefield(triggerList.allCards());
|
||||
final TriggerHandler trigHandler = game.getTriggerHandler();
|
||||
final Card lki = CardUtil.getLKICopy(hostCard);
|
||||
lki.clearControllers();
|
||||
lki.setOwner(sa.getActivatingPlayer());
|
||||
|
||||
return new GameCommand() {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -678,6 +675,29 @@ public abstract class SpellAbilityEffect {
|
||||
if (newCard == null || !newCard.equalsWithTimestamp(c)) {
|
||||
continue;
|
||||
}
|
||||
Trigger trig = null;
|
||||
if (sa.hasAdditionalAbility("ReturnAbility")) {
|
||||
String valid = sa.getParamOrDefault("ReturnValid", "Card.IsTriggerRemembered");
|
||||
|
||||
String trigSA = "Mode$ ChangesZone | Origin$ " + cell.getColumnKey() + " | Destination$ " + cell.getRowKey() + " | ValidCard$ " + valid +
|
||||
" | TriggerDescription$ " + sa.getAdditionalAbility("ReturnAbility").getParam("SpellDescription");
|
||||
|
||||
trig = TriggerHandler.parseTrigger(trigSA, hostCard, sa.isIntrinsic(), null);
|
||||
trig.setSpawningAbility(sa.copy(lki, sa.getActivatingPlayer(), true));
|
||||
trig.setActiveZone(null);
|
||||
trig.addRemembered(newCard);
|
||||
|
||||
SpellAbility overridingSA = sa.getAdditionalAbility("ReturnAbility").copy(hostCard, sa.getActivatingPlayer(), false);
|
||||
// need to reset the parent, additionalAbility does set it to this
|
||||
if (overridingSA instanceof AbilitySub) {
|
||||
((AbilitySub)overridingSA).setParent(null);
|
||||
}
|
||||
|
||||
trig.setOverridingAbility(overridingSA);
|
||||
|
||||
// Delayed Trigger should only happen once, no need for cleanup?
|
||||
trigHandler.registerThisTurnDelayedTrigger(trig);
|
||||
}
|
||||
// no cause there?
|
||||
Card movedCard = game.getAction().moveTo(cell.getRowKey(), newCard, 0, null, moveParams);
|
||||
untilTable.put(cell.getColumnKey(), cell.getRowKey(), movedCard);
|
||||
|
||||
@@ -34,7 +34,7 @@ public class AbandonEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("RememberAbandoned")) {
|
||||
source.addRemembered(source);
|
||||
}
|
||||
|
||||
|
||||
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
|
||||
controller.getZone(ZoneType.Command).remove(source);
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
|
||||
|
||||
@@ -206,8 +206,9 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<Card> tgts = getCardsfromTargets(sa);
|
||||
final boolean justOne = tgts.size() == 1;
|
||||
final List<Card> tgts = getDefinedCardsOrTargeted(sa);
|
||||
//possible to be building stack desc before Defined is populated... for now, 0 will default to singular
|
||||
final boolean justOne = tgts.size() <= 1;
|
||||
|
||||
if (sa.hasParam("IfDesc")) {
|
||||
if (sa.getParam("IfDesc").equals("True") && sa.hasParam("SpellDescription")) {
|
||||
@@ -264,7 +265,7 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
} else {
|
||||
sb.append("toughness ").append(toughness).append(" ");
|
||||
}
|
||||
} else {
|
||||
} else if (sb.length() > initial) {
|
||||
sb.append(justOne ? "becomes " : "become ");
|
||||
}
|
||||
|
||||
@@ -291,7 +292,8 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
}
|
||||
}
|
||||
if (keywords.size() > 0) {
|
||||
sb.append("and gains ").append(Lang.joinHomogenous(keywords).toLowerCase()).append(" ");
|
||||
sb.append(sb.length() > initial ? "and " : "").append(" gains ");
|
||||
sb.append(Lang.joinHomogenous(keywords).toLowerCase()).append(" ");
|
||||
}
|
||||
// sb.append(abilities)
|
||||
// sb.append(triggers)
|
||||
@@ -317,6 +319,20 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
}
|
||||
sb.append(".");
|
||||
|
||||
if (sa.hasParam("AtEOT")) {
|
||||
sb.append(" ");
|
||||
final String eot = sa.getParam("AtEOT");
|
||||
final String pronoun = justOne ? "it" : "them";
|
||||
if (eot.equals("Hand")) {
|
||||
sb.append("Return ").append(pronoun).append(" to your hand");
|
||||
} else if (eot.equals("SacrificeCtrl")) {
|
||||
sb.append(justOne ? "Its controller sacrifices it" : "Their controllers sacrifice them");
|
||||
} else { //Sacrifice,Exile
|
||||
sb.append(eot).append(" ").append(pronoun);
|
||||
}
|
||||
sb.append(" at the beginning of the next end step.");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ public class AttachEffect extends SpellAbilityEffect {
|
||||
choices = AbilityUtils.getDefinedEntities(source, sa.getParam("PlayerChoices"), sa);
|
||||
for (final Card attachment : attachments) {
|
||||
for (GameEntity g : choices) {
|
||||
if (!g.canBeAttached(attachment)) {
|
||||
if (!g.canBeAttached(attachment, sa)) {
|
||||
choices.remove(g);
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ public class AttachEffect extends SpellAbilityEffect {
|
||||
if (e != null)
|
||||
cardChoices.remove(e);
|
||||
}
|
||||
cardChoices = CardLists.filter(cardChoices, CardPredicates.canBeAttached(attachment));
|
||||
cardChoices = CardLists.filter(cardChoices, CardPredicates.canBeAttached(attachment, sa));
|
||||
}
|
||||
choices.addAll(cardChoices);
|
||||
}
|
||||
@@ -138,7 +138,7 @@ public class AttachEffect extends SpellAbilityEffect {
|
||||
// TODO add params for message
|
||||
continue;
|
||||
|
||||
attachment.attachToEntity(attachTo);
|
||||
attachment.attachToEntity(attachTo, sa);
|
||||
if (sa.hasParam("RememberAttached") && attachment.isAttachedToEntity(attachTo)) {
|
||||
source.addRemembered(attachment);
|
||||
}
|
||||
|
||||
@@ -35,13 +35,13 @@ public class BalanceEffect extends SpellAbilityEffect {
|
||||
Game game = activator.getGame();
|
||||
String valid = sa.getParamOrDefault("Valid", "Card");
|
||||
ZoneType zone = sa.hasParam("Zone") ? ZoneType.smartValueOf(sa.getParam("Zone")) : ZoneType.Battlefield;
|
||||
|
||||
|
||||
int min = Integer.MAX_VALUE;
|
||||
|
||||
|
||||
final FCollectionView<Player> players = game.getPlayersInTurnOrder();
|
||||
final List<CardCollection> validCards = new ArrayList<>(players.size());
|
||||
Map<Player, CardCollectionView> discardedMap = Maps.newHashMap();
|
||||
|
||||
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
// Find the minimum of each Valid per player
|
||||
validCards.add(CardLists.getValidCards(players.get(i).getCardsIn(zone), valid, activator, source, sa));
|
||||
|
||||
@@ -33,7 +33,7 @@ public class BidLifeEffect extends SpellAbilityEffect {
|
||||
} else {
|
||||
startBidding = 0;
|
||||
}
|
||||
|
||||
|
||||
if (sa.hasParam("OtherBidder")) {
|
||||
bidPlayers.add(activator);
|
||||
bidPlayers.addAll(AbilityUtils.getDefinedPlayers(host, sa.getParam("OtherBidder"), sa));
|
||||
@@ -62,7 +62,7 @@ public class BidLifeEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
host.setChosenNumber(bid);
|
||||
host.addRemembered(winner);
|
||||
final SpellAbility action = sa.getAdditionalAbility("BidSubAbility");
|
||||
|
||||
@@ -192,6 +192,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
if (host == null) {
|
||||
host = sa.getHostCard();
|
||||
}
|
||||
host.addExiledCard(movedCard);
|
||||
movedCard.setExiledWith(host);
|
||||
movedCard.setExiledBy(host.getController());
|
||||
}
|
||||
@@ -260,7 +261,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
|
||||
if (sa.hasParam("Duration")) {
|
||||
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, source));
|
||||
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, sa));
|
||||
}
|
||||
|
||||
// if Shuffle parameter exists, and any amount of cards were owned by
|
||||
|
||||
@@ -138,12 +138,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
final String ct = sa.getParam("ChangeType");
|
||||
type = CardType.CoreType.isValidEnum(ct) ? ct.toLowerCase() : ct;
|
||||
}
|
||||
final String cardTag = type.contains("card") ? "" : " card";
|
||||
|
||||
final int num = sa.hasParam("ChangeNum") ? AbilityUtils.calculateAmount(host,
|
||||
sa.getParam("ChangeNum"), sa) : 1;
|
||||
boolean tapped = sa.hasParam("Tapped");
|
||||
boolean attacking = sa.hasParam("Attacking");
|
||||
if (sa.hasParam("Ninjutsu")) {
|
||||
if (sa.isNinjutsu()) {
|
||||
tapped = true;
|
||||
attacking = true;
|
||||
}
|
||||
@@ -170,7 +171,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
} else {
|
||||
sb.append(" for ");
|
||||
}
|
||||
final String cardTag = type.contains("card") ? "" : " card";
|
||||
sb.append(Lang.nounWithNumeralExceptOne(num, type + cardTag)).append(", ");
|
||||
if (!sa.hasParam("NoReveal") && ZoneType.smartValueOf(destination).isHidden()) {
|
||||
if (choosers.size() == 1) {
|
||||
@@ -196,8 +196,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
if (destination.equals("Battlefield")) {
|
||||
sb.append("onto the battlefield");
|
||||
if (tapped) {
|
||||
sb.append(" tapped");
|
||||
sb.append(" tapped").append(attacking ? " and" : "");
|
||||
}
|
||||
sb.append(attacking ? " attacking" : "");
|
||||
if (sa.hasParam("GainControl")) {
|
||||
sb.append(" under ").append(chooserNames).append("'s control");
|
||||
}
|
||||
@@ -259,14 +260,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
if (destination.equals("Battlefield")) {
|
||||
sb.append(" onto the battlefield");
|
||||
if (tapped) {
|
||||
sb.append(" tapped");
|
||||
if (attacking) {
|
||||
sb.append(" and");
|
||||
}
|
||||
}
|
||||
if (attacking) {
|
||||
sb.append(" attacking");
|
||||
sb.append(" tapped").append(attacking ? " and" : "");
|
||||
}
|
||||
sb.append(attacking ? " attacking" : "");
|
||||
if (sa.hasParam("GainControl")) {
|
||||
sb.append(" under ").append(chooserNames).append("'s control");
|
||||
}
|
||||
@@ -293,14 +289,38 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
// TODO Expand on this Description as more cards use it
|
||||
// for the non-targeted SAs when you choose what is returned on resolution
|
||||
sb.append("Return ").append(num).append(" ").append(type).append(" card(s) ");
|
||||
sb.append(" to your ").append(destination);
|
||||
sb.append(" to your ").append(destination).append(".");
|
||||
} else if (origin.equals("Graveyard")) {
|
||||
// for non-targeted SAs when you choose what is moved on resolution
|
||||
// this will need expansion as more cards use it
|
||||
sb.append(chooserNames).append(" puts ");
|
||||
final String cardTag = type.contains("card") ? "" : " card";
|
||||
sb.append(num != 0 ? Lang.nounWithNumeralExceptOne(num, type + cardTag) :
|
||||
sa.getParamOrDefault("ChangeNumDesc", "") + " " + type + cardTag);
|
||||
final boolean changeNumDesc = sa.hasParam("ChangeNumDesc");
|
||||
final boolean mandatory = sa.hasParam("Mandatory");
|
||||
String changed;
|
||||
if (changeNumDesc) {
|
||||
changed = sa.getParam("ChangeNumDesc") + " " + type + cardTag;
|
||||
} else if (!mandatory) {
|
||||
changed = Lang.nounWithNumeral(num, type + cardTag);
|
||||
} else {
|
||||
changed = Lang.nounWithNumeralExceptOne(num, type + cardTag);
|
||||
}
|
||||
final boolean battlefield = destination.equals("Battlefield");
|
||||
sb.append(chooserNames).append(" returns ").append(mandatory || changeNumDesc ? "" : "up to ");
|
||||
sb.append(changed);
|
||||
// so far, it seems non-targeted only lets you return from your own graveyard
|
||||
sb.append(" from their graveyard").append(choosers.size() > 1 ? "s" : "");
|
||||
sb.append(battlefield ? " to the " : " into their ").append(destination.toLowerCase());
|
||||
if (sa.hasParam("WithCountersType")) {
|
||||
final CounterType cType = CounterType.getType(sa.getParam("WithCountersType"));
|
||||
if (cType != null) {
|
||||
sb.append(" with an additional ").append(cType.getName()).append(" counter on it");
|
||||
} else {
|
||||
sb.append(" [ChangeZoneEffect WithCountersType error]");
|
||||
}
|
||||
}
|
||||
sb.append(".");
|
||||
} else if (origin.equals("Exile")) {
|
||||
// for non-targeted, moved cards are chosen on resolution – will need expansion as more cards use it
|
||||
sb.append(chooserNames).append(" puts ").append(Lang.nounWithNumeralExceptOne(num, type + cardTag));
|
||||
sb.append(" into their ").append(destination.toLowerCase()).append(".");
|
||||
}
|
||||
|
||||
@@ -342,14 +362,16 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
final String fromGraveyard = " from the graveyard";
|
||||
|
||||
if (destination.equals(ZoneType.Battlefield)) {
|
||||
final boolean attacking = (sa.hasParam("Attacking"));
|
||||
if (ZoneType.Graveyard.equals(origin)) {
|
||||
sb.append("Return").append(targetname).append(fromGraveyard).append(" to the battlefield");
|
||||
} else {
|
||||
sb.append("Put").append(targetname).append(" onto the battlefield");
|
||||
}
|
||||
if (sa.hasParam("Tapped")) {
|
||||
sb.append(" tapped");
|
||||
sb.append(" tapped").append(attacking ? " and" : "");
|
||||
}
|
||||
sb.append(attacking ? " attacking" : "");
|
||||
if (sa.hasParam("GainControl")) {
|
||||
sb.append(" under your control");
|
||||
}
|
||||
@@ -431,7 +453,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sa.isHidden() && !sa.hasParam("Ninjutsu")) {
|
||||
if (sa.isHidden() && !sa.isNinjutsu()) {
|
||||
changeHiddenOriginResolve(sa);
|
||||
} else {
|
||||
//else if (isKnown(origin) || sa.containsKey("Ninjutsu")) {
|
||||
@@ -575,7 +597,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
movedCard = game.getAction().moveToLibrary(gameCard, libraryPosition, sa);
|
||||
} else {
|
||||
if (destination.equals(ZoneType.Battlefield)) {
|
||||
if (sa.hasParam("Tapped") || sa.hasParam("Ninjutsu")) {
|
||||
if (sa.hasParam("Tapped") || sa.isNinjutsu()) {
|
||||
gameCard.setTapped(true);
|
||||
}
|
||||
if (sa.hasParam("Untapped")) {
|
||||
@@ -595,10 +617,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
gameCard.addEtbCounter(cType, cAmount, player);
|
||||
}
|
||||
if (sa.hasParam("GainControl")) {
|
||||
Player newController = player;
|
||||
if (sa.hasParam("NewController")) {
|
||||
newController = Iterables.getFirst(AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("NewController"), sa), null);
|
||||
}
|
||||
final String g = sa.getParam("GainControl");
|
||||
Player newController = g.equals("True") ? sa.getActivatingPlayer() :
|
||||
AbilityUtils.getDefinedPlayers(sa.getHostCard(), g, sa).get(0);
|
||||
if (newController != null) {
|
||||
if (newController != gameCard.getController()) {
|
||||
gameCard.runChangeControllerCommands();
|
||||
@@ -615,7 +636,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
// only valid choices are when they could be attached
|
||||
// TODO for multiple Auras entering attached this way, need to use LKI info
|
||||
if (!list.isEmpty()) {
|
||||
list = CardLists.filter(list, CardPredicates.canBeAttached(gameCard));
|
||||
list = CardLists.filter(list, CardPredicates.canBeAttached(gameCard, sa));
|
||||
}
|
||||
if (!list.isEmpty()) {
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
@@ -624,7 +645,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
|
||||
// TODO can't attach later or moveToPlay would attach indirectly
|
||||
// bypass canBeAttached to skip Protection checks when trying to attach multiple auras that would grant protection
|
||||
gameCard.attachToEntity(game.getCardState(attachedTo), true);
|
||||
gameCard.attachToEntity(game.getCardState(attachedTo), sa, true);
|
||||
} else { // When it should enter the battlefield attached to an illegal permanent it fails
|
||||
continue;
|
||||
}
|
||||
@@ -636,7 +657,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Attach", gameCard);
|
||||
Player attachedTo = player.getController().chooseSingleEntityForEffect(list, sa, Localizer.getInstance().getMessage("lblSelectAPlayerAttachSourceTo", gameCard.toString()), params);
|
||||
gameCard.attachToEntity(attachedTo);
|
||||
gameCard.attachToEntity(attachedTo, sa);
|
||||
}
|
||||
else { // When it should enter the battlefield attached to an illegal player it fails
|
||||
continue;
|
||||
@@ -689,7 +710,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
if (addToCombat(movedCard, movedCard.getController(), sa, "Attacking", "Blocking")) {
|
||||
combatChanged = true;
|
||||
}
|
||||
if (sa.hasParam("Ninjutsu")) {
|
||||
if (sa.isNinjutsu()) {
|
||||
// Ninjutsu need to get the Defender of the Returned Creature
|
||||
final Card returned = sa.getPaidList("Returned").getFirst();
|
||||
final GameEntity defender = game.getCombat().getDefenderByAttacker(returned);
|
||||
@@ -708,7 +729,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Attach", gameCard);
|
||||
Card attachedTo = chooser.getController().chooseSingleEntityForEffect(list, sa, title, params);
|
||||
movedCard.attachToEntity(attachedTo);
|
||||
movedCard.attachToEntity(attachedTo, sa);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -719,6 +740,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
if (host == null) {
|
||||
host = sa.getHostCard();
|
||||
}
|
||||
host.addExiledCard(gameCard);
|
||||
gameCard.setExiledWith(host);
|
||||
gameCard.setExiledBy(host.getController());
|
||||
}
|
||||
@@ -745,6 +767,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
if (destination.equals(ZoneType.Exile) && !movedCard.isToken()) {
|
||||
movedCard.setExiledWith(host);
|
||||
if (host != null) {
|
||||
host.addExiledCard(movedCard);
|
||||
movedCard.setExiledBy(host.getController());
|
||||
}
|
||||
}
|
||||
@@ -844,7 +867,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
registerDelayedTrigger(sa, sa.getParam("AtEOT"), triggerList.allCards());
|
||||
}
|
||||
if ("UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
|
||||
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, hostCard));
|
||||
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, sa));
|
||||
}
|
||||
|
||||
// for things like Gaea's Blessing
|
||||
@@ -1253,10 +1276,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
source.removeRemembered(c);
|
||||
}
|
||||
if (sa.hasParam("GainControl")) {
|
||||
Player newController = sa.getActivatingPlayer();
|
||||
if (sa.hasParam("NewController")) {
|
||||
newController = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("NewController"), sa).get(0);
|
||||
}
|
||||
final String g = sa.getParam("GainControl");
|
||||
Player newController = g.equals("True") ? sa.getActivatingPlayer() :
|
||||
AbilityUtils.getDefinedPlayers(sa.getHostCard(), g, sa).get(0);
|
||||
if (newController != c.getController()) {
|
||||
c.runChangeControllerCommands();
|
||||
}
|
||||
@@ -1285,7 +1307,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
// only valid choices are when they could be attached
|
||||
// TODO for multiple Auras entering attached this way, need to use LKI info
|
||||
if (!list.isEmpty()) {
|
||||
list = CardLists.filter(list, CardPredicates.canBeAttached(c));
|
||||
list = CardLists.filter(list, CardPredicates.canBeAttached(c, sa));
|
||||
}
|
||||
if (!list.isEmpty()) {
|
||||
String title = Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(c.getName()));
|
||||
@@ -1295,7 +1317,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
|
||||
// TODO can't attach later or moveToPlay would attach indirectly
|
||||
// bypass canBeAttached to skip Protection checks when trying to attach multiple auras that would grant protection
|
||||
c.attachToEntity(game.getCardState(attachedTo), true);
|
||||
c.attachToEntity(game.getCardState(attachedTo), sa, true);
|
||||
}
|
||||
else { // When it should enter the battlefield attached to an illegal permanent it fails
|
||||
continue;
|
||||
@@ -1309,7 +1331,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Attach", c);
|
||||
Player attachedTo = player.getController().chooseSingleEntityForEffect(list, sa, title, params);
|
||||
c.attachToEntity(attachedTo);
|
||||
c.attachToEntity(attachedTo, sa);
|
||||
}
|
||||
else { // When it should enter the battlefield attached to an illegal permanent it fails
|
||||
continue;
|
||||
@@ -1339,7 +1361,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Attach", movedCard);
|
||||
Card attachedTo = decider.getController().chooseSingleEntityForEffect(list, sa, title, params);
|
||||
movedCard.attachToEntity(attachedTo);
|
||||
movedCard.attachToEntity(attachedTo, sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1350,6 +1372,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
if (host == null) {
|
||||
host = sa.getHostCard();
|
||||
}
|
||||
host.addExiledCard(movedCard);
|
||||
movedCard.setExiledWith(host);
|
||||
movedCard.setExiledBy(host.getController());
|
||||
}
|
||||
@@ -1409,6 +1432,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("RememberLKI")) {
|
||||
source.addRemembered(CardUtil.getLKICopy(c));
|
||||
}
|
||||
if (forget) {
|
||||
source.removeRemembered(movedCard);
|
||||
}
|
||||
@@ -1446,7 +1472,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
|
||||
if ("UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
|
||||
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, source));
|
||||
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, sa));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1541,6 +1567,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
host = srcSA.getHostCard();
|
||||
}
|
||||
movedCard = game.getAction().exile(tgtHost, srcSA, params);
|
||||
host.addExiledCard(movedCard);
|
||||
movedCard.setExiledWith(host);
|
||||
movedCard.setExiledBy(host.getController());
|
||||
} else if (srcSA.getParam("Destination").equals("TopOfLibrary")) {
|
||||
|
||||
@@ -42,7 +42,7 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
||||
}
|
||||
final SpellAbility fallback = sa.getAdditionalAbility("FallbackAbility");
|
||||
final int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("ChoiceAmount", "1"), sa);
|
||||
|
||||
|
||||
final List<Player> tgtPlayers = getDefinedPlayersOrTargeted(sa);
|
||||
|
||||
for (final Player p : tgtPlayers) {
|
||||
@@ -61,7 +61,7 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
abilities.removeAll(saToRemove);
|
||||
|
||||
|
||||
if (sa.usesTargeting() && sa.getTargets().contains(p) && !p.canBeTargetedBy(sa)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.game.ability.effects;
|
||||
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
@@ -18,7 +19,12 @@ public class CleanUpEffect extends SpellAbilityEffect {
|
||||
*/
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
Card source = sa.getHostCard();
|
||||
Card source;
|
||||
if (sa.hasParam("Defined")) {
|
||||
source = getDefinedCardsOrTargeted(sa).get(0);
|
||||
} else {
|
||||
source = sa.getHostCard();
|
||||
}
|
||||
final Game game = source.getGame();
|
||||
|
||||
String logMessage = "";
|
||||
@@ -31,13 +37,8 @@ public class CleanUpEffect extends SpellAbilityEffect {
|
||||
game.getCardState(source).clearRemembered();
|
||||
}
|
||||
if (sa.hasParam("ForgetDefined")) {
|
||||
for (final Card card : AbilityUtils.getDefinedCards(source, sa.getParam("ForgetDefined"), sa)) {
|
||||
source.removeRemembered(card);
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("ForgetDefinedPlayer")) {
|
||||
for (final Player player : AbilityUtils.getDefinedPlayers(source, sa.getParam("ForgetDefinedPlayer"), sa)) {
|
||||
source.removeRemembered(player);
|
||||
for (final GameEntity ge : AbilityUtils.getDefinedEntities(source, sa.getParam("ForgetDefined"), sa)) {
|
||||
source.removeRemembered(ge);
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("ClearImprinted")) {
|
||||
|
||||
@@ -12,7 +12,10 @@ import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Lang;
|
||||
import forge.util.Localizer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -24,13 +27,14 @@ public class ConniveEffect extends SpellAbilityEffect {
|
||||
*/
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
List<Card> tgt = getTargetCards(sa);
|
||||
|
||||
if (tgt.size() <= 0) {
|
||||
return "";
|
||||
} else {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(Lang.joinHomogenous(tgt)).append(tgt.size() > 1 ? " connive." : " connives.");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -39,55 +43,70 @@ public class ConniveEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player hostCon = host.getController();
|
||||
final Game game = host.getGame();
|
||||
final int num = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("ConniveNum", "1"), sa);
|
||||
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
final CardZoneTable triggerList = new CardZoneTable();
|
||||
Map<Player, CardCollectionView> discardedMap = Maps.newHashMap();
|
||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
|
||||
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
|
||||
CardCollection toConnive = getTargetCards(sa);
|
||||
if (toConnive.isEmpty()) { // if nothing is conniving, we're done
|
||||
return;
|
||||
}
|
||||
|
||||
for (final Card c : getTargetCards(sa)) {
|
||||
final Player p = c.getController();
|
||||
|
||||
p.drawCards(num, sa, moveParams);
|
||||
|
||||
CardCollectionView dPHand = p.getCardsIn(ZoneType.Hand);
|
||||
dPHand = CardLists.filter(dPHand, CardPredicates.Presets.NON_TOKEN);
|
||||
if (dPHand.isEmpty()) { // seems unlikely, but just to be safe
|
||||
continue; // for loop over players
|
||||
}
|
||||
|
||||
CardCollection validCards = CardLists.getValidCards(dPHand, "Card", hostCon, host, sa);
|
||||
|
||||
if (!p.canDiscardBy(sa, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int amt = Math.min(validCards.size(), num);
|
||||
CardCollectionView toBeDiscarded = amt == 0 ? CardCollection.EMPTY :
|
||||
p.getController().chooseCardsToDiscardFrom(p, sa, validCards, amt, amt);
|
||||
|
||||
if (toBeDiscarded.size() > 1) {
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||
}
|
||||
|
||||
discardedMap.put(p, toBeDiscarded);
|
||||
|
||||
int numCntrs = CardLists.getValidCardCount(toBeDiscarded, "Card.nonLand", hostCon, host, sa);
|
||||
|
||||
// need to get newest game state to check if it is still on the battlefield and the timestamp didn't change
|
||||
Card gamec = game.getCardState(c);
|
||||
// if the card is not in the game anymore, this might still return true, but it's no problem
|
||||
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.equalsWithTimestamp(c)) {
|
||||
c.addCounter(CounterEnumType.P1P1, numCntrs, p, table);
|
||||
List<Player> controllers = new ArrayList<>();
|
||||
for (Card c : toConnive) {
|
||||
final Player controller = c.getController();
|
||||
if (!controllers.contains(controller)) {
|
||||
controllers.add(controller);
|
||||
}
|
||||
}
|
||||
//order controllers by APNAP
|
||||
int indexAP = controllers.indexOf(game.getPhaseHandler().getPlayerTurn());
|
||||
if (indexAP != -1) {
|
||||
Collections.rotate(controllers, - indexAP);
|
||||
}
|
||||
|
||||
for (final Player p : controllers) {
|
||||
CardCollection connivers = CardLists.filterControlledBy(toConnive, p);
|
||||
while (connivers.size() > 0) {
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
final CardZoneTable triggerList = new CardZoneTable();
|
||||
Map<Player, CardCollectionView> discardedMap = Maps.newHashMap();
|
||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
|
||||
moveParams.put(AbilityKey.LastStateGraveyard, sa.getLastStateGraveyard());
|
||||
|
||||
Card conniver = connivers.size() > 1 ? p.getController().chooseSingleEntityForEffect(connivers, sa,
|
||||
Localizer.getInstance().getMessage("lblChooseConniver"), null) : toConnive.get(0);
|
||||
|
||||
p.drawCards(num, sa, moveParams);
|
||||
|
||||
CardCollection validDisards =
|
||||
CardLists.filter(p.getCardsIn(ZoneType.Hand), CardPredicates.Presets.NON_TOKEN);
|
||||
if (validDisards.isEmpty() || !p.canDiscardBy(sa, true)) { // hand being empty unlikely, just to be safe
|
||||
continue;
|
||||
}
|
||||
|
||||
int amt = Math.min(validDisards.size(), num);
|
||||
CardCollectionView toBeDiscarded = amt == 0 ? CardCollection.EMPTY :
|
||||
p.getController().chooseCardsToDiscardFrom(p, sa, validDisards, amt, amt);
|
||||
|
||||
if (toBeDiscarded.size() > 1) {
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||
}
|
||||
|
||||
int numCntrs = CardLists.getValidCardCount(toBeDiscarded, "Card.nonLand", p, host, sa);
|
||||
|
||||
// need to get newest game state to check if it is still on the battlefield and the timestamp didn't change
|
||||
Card gamec = game.getCardState(conniver);
|
||||
// if the card is not in the game anymore, this might still return true, but it's no problem
|
||||
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.equalsWithTimestamp(conniver)) {
|
||||
conniver.addCounter(CounterEnumType.P1P1, numCntrs, p, table);
|
||||
}
|
||||
connivers.remove(conniver);
|
||||
discardedMap.put(p, CardCollection.getView(toBeDiscarded));
|
||||
discard(sa, triggerList, true, discardedMap, moveParams);
|
||||
table.replaceCounterEffect(game, sa, true);
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
}
|
||||
}
|
||||
discard(sa, triggerList, true, discardedMap, moveParams);
|
||||
table.replaceCounterEffect(game, sa, true);
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.GameCommand;
|
||||
@@ -17,6 +14,10 @@ import forge.game.event.GameEventCombatChanged;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Localizer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ControlGainEffect extends SpellAbilityEffect {
|
||||
|
||||
@@ -102,6 +103,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
Card source = sa.getHostCard();
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
|
||||
final boolean bUntap = sa.hasParam("Untap");
|
||||
final boolean bTapOnLose = sa.hasParam("TapOnLose");
|
||||
@@ -112,13 +114,26 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
|
||||
final List<Player> controllers = getDefinedPlayersOrTargeted(sa, "NewController");
|
||||
|
||||
final Player newController = controllers.isEmpty() ? sa.getActivatingPlayer() : controllers.get(0);
|
||||
final Player newController = controllers.isEmpty() ? activator : controllers.get(0);
|
||||
final Game game = newController.getGame();
|
||||
|
||||
CardCollectionView tgtCards = getDefinedCards(sa);
|
||||
CardCollectionView tgtCards = null;
|
||||
if (sa.hasParam("Choices")) {
|
||||
Player chooser = sa.hasParam("Chooser") ? AbilityUtils.getDefinedPlayers(source,
|
||||
sa.getParam("Chooser"), sa).get(0) : activator;
|
||||
CardCollectionView choices = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield),
|
||||
sa.getParam("Choices"), activator, source, sa);
|
||||
if (!choices.isEmpty()) {
|
||||
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") :
|
||||
Localizer.getInstance().getMessage("lblChooseaCard") +" ";
|
||||
tgtCards = chooser.getController().chooseCardsForEffect(choices, sa, title, 1, 1, false, null);
|
||||
}
|
||||
} else {
|
||||
tgtCards = getDefinedCards(sa);
|
||||
}
|
||||
|
||||
if (sa.hasParam("ControlledByTarget")) {
|
||||
tgtCards = CardLists.filterControlledBy(tgtCards, getTargetPlayers(sa));
|
||||
if (tgtCards != null & sa.hasParam("ControlledByTarget")) {
|
||||
tgtCards = CardLists.filterControlledBy(tgtCards, getTargetPlayers(sa));
|
||||
}
|
||||
|
||||
// in case source was LKI or still resolving
|
||||
|
||||
@@ -33,7 +33,6 @@ import forge.util.Aggregates;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.PredicateString.StringOp;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
public class CopyPermanentEffect extends TokenEffectBase {
|
||||
|
||||
@@ -68,7 +67,9 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
|
||||
if (sa.hasParam("AddTriggers")) {
|
||||
final String oDesc = sa.getDescription();
|
||||
final String trigStg = oDesc.substring(oDesc.indexOf("\""),oDesc.lastIndexOf("\"") + 1);
|
||||
final String trigStg = oDesc.contains("\"") ?
|
||||
oDesc.substring(oDesc.indexOf("\""),oDesc.lastIndexOf("\"") + 1) :
|
||||
"[trigger text parsing error]";
|
||||
if (addKWs) {
|
||||
sb.append(" and ").append(trigStg);
|
||||
} else {
|
||||
@@ -103,112 +104,6 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
final Game game = host.getGame();
|
||||
|
||||
if (sa.hasParam("Optional") && !activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblCopyPermanentConfirm"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int numCopies = sa.hasParam("NumCopies") ? AbilityUtils.calculateAmount(host, sa.getParam("NumCopies"), sa) : 1;
|
||||
|
||||
Player controller = null;
|
||||
if (sa.hasParam("Controller")) {
|
||||
final FCollectionView<Player> defined = AbilityUtils.getDefinedPlayers(host, sa.getParam("Controller"), sa);
|
||||
if (!defined.isEmpty()) {
|
||||
controller = defined.getFirst();
|
||||
}
|
||||
}
|
||||
if (controller == null) {
|
||||
controller = activator;
|
||||
}
|
||||
|
||||
List<Card> tgtCards = Lists.newArrayList();
|
||||
|
||||
if (sa.hasParam("ValidSupportedCopy")) {
|
||||
List<PaperCard> cards = Lists.newArrayList(StaticData.instance().getCommonCards().getUniqueCards());
|
||||
String valid = sa.getParam("ValidSupportedCopy");
|
||||
if (valid.contains("X")) {
|
||||
valid = TextUtil.fastReplace(valid,
|
||||
"X", Integer.toString(AbilityUtils.calculateAmount(host, "X", sa)));
|
||||
}
|
||||
if (StringUtils.containsIgnoreCase(valid, "creature")) {
|
||||
Predicate<PaperCard> cpp = Predicates.compose(CardRulesPredicates.Presets.IS_CREATURE, PaperCard.FN_GET_RULES);
|
||||
cards = Lists.newArrayList(Iterables.filter(cards, cpp));
|
||||
}
|
||||
if (StringUtils.containsIgnoreCase(valid, "equipment")) {
|
||||
Predicate<PaperCard> cpp = Predicates.compose(CardRulesPredicates.Presets.IS_EQUIPMENT, PaperCard.FN_GET_RULES);
|
||||
cards = Lists.newArrayList(Iterables.filter(cards, cpp));
|
||||
}
|
||||
if (sa.hasParam("RandomCopied")) {
|
||||
List<PaperCard> copysource = Lists.newArrayList(cards);
|
||||
List<Card> choice = Lists.newArrayList();
|
||||
final String num = sa.getParamOrDefault("RandomNum", "1");
|
||||
int ncopied = AbilityUtils.calculateAmount(host, num, sa);
|
||||
while (ncopied > 0 && !copysource.isEmpty()) {
|
||||
final PaperCard cp = Aggregates.random(copysource);
|
||||
Card possibleCard = Card.fromPaperCard(cp, activator); // Need to temporarily set the Owner so the Game is set
|
||||
|
||||
if (possibleCard.isValid(valid, host.getController(), host, sa)) {
|
||||
if (host.getController().isAI() && possibleCard.getRules() != null && possibleCard.getRules().getAiHints().getRemAIDecks())
|
||||
continue;
|
||||
choice.add(possibleCard);
|
||||
ncopied -= 1;
|
||||
}
|
||||
copysource.remove(cp);
|
||||
}
|
||||
tgtCards = choice;
|
||||
|
||||
System.err.println("Copying random permanent(s): " + tgtCards.toString());
|
||||
} else if (sa.hasParam("DefinedName")) {
|
||||
String name = sa.getParam("DefinedName");
|
||||
if (name.equals("NamedCard")) {
|
||||
if (!host.getNamedCard().isEmpty()) {
|
||||
name = host.getNamedCard();
|
||||
}
|
||||
}
|
||||
|
||||
Predicate<PaperCard> cpp = Predicates.compose(CardRulesPredicates.name(StringOp.EQUALS, name), PaperCard.FN_GET_RULES);
|
||||
cards = Lists.newArrayList(Iterables.filter(cards, cpp));
|
||||
|
||||
if (!cards.isEmpty()) {
|
||||
tgtCards.add(Card.fromPaperCard(cards.get(0), controller));
|
||||
}
|
||||
}
|
||||
} else if (sa.hasParam("Choices")) {
|
||||
Player chooser = activator;
|
||||
if (sa.hasParam("Chooser")) {
|
||||
final String choose = sa.getParam("Chooser");
|
||||
chooser = AbilityUtils.getDefinedPlayers(sa.getHostCard(), choose, sa).get(0);
|
||||
}
|
||||
|
||||
// For Mimic Vat with mutated creature, need to choose one imprinted card
|
||||
CardCollectionView choices = sa.hasParam("Defined") ? getDefinedCardsOrTargeted(sa) : game.getCardsIn(ZoneType.Battlefield);
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host, sa);
|
||||
if (!choices.isEmpty()) {
|
||||
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseaCard");
|
||||
|
||||
if (sa.hasParam("WithDifferentNames")) {
|
||||
// any Number of choices with different names
|
||||
while (!choices.isEmpty()) {
|
||||
Card choosen = chooser.getController().chooseSingleEntityForEffect(choices, sa, title, true, null);
|
||||
|
||||
if (choosen != null) {
|
||||
tgtCards.add(choosen);
|
||||
choices = CardLists.filter(choices, Predicates.not(CardPredicates.sharesNameWith(choosen)));
|
||||
} else if (chooser.getController().confirmAction(sa, PlayerActionConfirmMode.OptionalChoose, Localizer.getInstance().getMessage("lblCancelChooseConfirm"))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Card choosen = chooser.getController().chooseSingleEntityForEffect(choices, sa, title, false, null);
|
||||
if (choosen != null) {
|
||||
tgtCards.add(choosen);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tgtCards = getDefinedCardsOrTargeted(sa);
|
||||
}
|
||||
|
||||
boolean useZoneTable = true;
|
||||
CardZoneTable triggerList = sa.getChangeZoneTable();
|
||||
if (triggerList == null) {
|
||||
@@ -223,13 +118,127 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
MutableBoolean combatChanged = new MutableBoolean(false);
|
||||
TokenCreateTable tokenTable = new TokenCreateTable();
|
||||
|
||||
for (final Card c : tgtCards) {
|
||||
// if it only targets player, it already got all needed cards from defined
|
||||
if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer() && !c.canBeTargetedBy(sa)) {
|
||||
continue;
|
||||
if (sa.hasParam("Optional") && !activator.getController().confirmAction(sa, null,
|
||||
Localizer.getInstance().getMessage("lblCopyPermanentConfirm"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int numCopies = sa.hasParam("NumCopies") ? AbilityUtils.calculateAmount(host,
|
||||
sa.getParam("NumCopies"), sa) : 1;
|
||||
|
||||
List<Player> controllers = Lists.newArrayList();
|
||||
if (sa.hasParam("Controller")) {
|
||||
controllers = AbilityUtils.getDefinedPlayers(host, sa.getParam("Controller"), sa);
|
||||
}
|
||||
if (controllers.isEmpty()) {
|
||||
controllers.add(activator);
|
||||
}
|
||||
|
||||
for (final Player controller : controllers) {
|
||||
List<Card> tgtCards = Lists.newArrayList();
|
||||
|
||||
if (sa.hasParam("ValidSupportedCopy")) {
|
||||
List<PaperCard> cards = Lists.newArrayList(StaticData.instance().getCommonCards().getUniqueCards());
|
||||
String valid = sa.getParam("ValidSupportedCopy");
|
||||
if (valid.contains("X")) {
|
||||
valid = TextUtil.fastReplace(valid,
|
||||
"X", Integer.toString(AbilityUtils.calculateAmount(host, "X", sa)));
|
||||
}
|
||||
if (StringUtils.containsIgnoreCase(valid, "creature")) {
|
||||
Predicate<PaperCard> cpp = Predicates.compose(CardRulesPredicates.Presets.IS_CREATURE, PaperCard.FN_GET_RULES);
|
||||
cards = Lists.newArrayList(Iterables.filter(cards, cpp));
|
||||
}
|
||||
if (StringUtils.containsIgnoreCase(valid, "equipment")) {
|
||||
Predicate<PaperCard> cpp = Predicates.compose(CardRulesPredicates.Presets.IS_EQUIPMENT, PaperCard.FN_GET_RULES);
|
||||
cards = Lists.newArrayList(Iterables.filter(cards, cpp));
|
||||
}
|
||||
if (sa.hasParam("RandomCopied")) {
|
||||
List<PaperCard> copysource = Lists.newArrayList(cards);
|
||||
List<Card> choice = Lists.newArrayList();
|
||||
final String num = sa.getParamOrDefault("RandomNum", "1");
|
||||
int ncopied = AbilityUtils.calculateAmount(host, num, sa);
|
||||
while (ncopied > 0 && !copysource.isEmpty()) {
|
||||
final PaperCard cp = Aggregates.random(copysource);
|
||||
Card possibleCard = Card.fromPaperCard(cp, activator); // Need to temporarily set the Owner so the Game is set
|
||||
|
||||
if (possibleCard.isValid(valid, host.getController(), host, sa)) {
|
||||
if (host.getController().isAI() && possibleCard.getRules() != null && possibleCard.getRules().getAiHints().getRemAIDecks())
|
||||
continue;
|
||||
choice.add(possibleCard);
|
||||
ncopied -= 1;
|
||||
}
|
||||
copysource.remove(cp);
|
||||
}
|
||||
tgtCards = choice;
|
||||
|
||||
System.err.println("Copying random permanent(s): " + tgtCards.toString());
|
||||
} else if (sa.hasParam("DefinedName")) {
|
||||
String name = sa.getParam("DefinedName");
|
||||
if (name.equals("NamedCard")) {
|
||||
if (!host.getNamedCard().isEmpty()) {
|
||||
name = host.getNamedCard();
|
||||
}
|
||||
}
|
||||
|
||||
Predicate<PaperCard> cpp = Predicates.compose(CardRulesPredicates.name(StringOp.EQUALS, name), PaperCard.FN_GET_RULES);
|
||||
cards = Lists.newArrayList(Iterables.filter(cards, cpp));
|
||||
|
||||
if (!cards.isEmpty()) {
|
||||
tgtCards.add(Card.fromPaperCard(cards.get(0), controller));
|
||||
}
|
||||
}
|
||||
} else if (sa.hasParam("Choices")) {
|
||||
Player chooser = activator;
|
||||
if (sa.hasParam("Chooser")) {
|
||||
final String choose = sa.getParam("Chooser");
|
||||
chooser = AbilityUtils.getDefinedPlayers(sa.getHostCard(), choose, sa).get(0);
|
||||
}
|
||||
|
||||
// For Mimic Vat with mutated creature, need to choose one imprinted card
|
||||
CardCollectionView choices = sa.hasParam("Defined") ? getDefinedCardsOrTargeted(sa) : game.getCardsIn(ZoneType.Battlefield);
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host, sa);
|
||||
if (!choices.isEmpty()) {
|
||||
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseaCard");
|
||||
|
||||
if (sa.hasParam("WithDifferentNames")) {
|
||||
// any Number of choices with different names
|
||||
while (!choices.isEmpty()) {
|
||||
Card choosen = chooser.getController().chooseSingleEntityForEffect(choices, sa, title, true, null);
|
||||
|
||||
if (choosen != null) {
|
||||
tgtCards.add(choosen);
|
||||
choices = CardLists.filter(choices, Predicates.not(CardPredicates.sharesNameWith(choosen)));
|
||||
} else if (chooser.getController().confirmAction(sa, PlayerActionConfirmMode.OptionalChoose, Localizer.getInstance().getMessage("lblCancelChooseConfirm"))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Card choosen = chooser.getController().chooseSingleEntityForEffect(choices, sa, title, false, null);
|
||||
if (choosen != null) {
|
||||
tgtCards.add(choosen);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tgtCards = getDefinedCardsOrTargeted(sa);
|
||||
}
|
||||
tokenTable.put(controller, getProtoType(sa, c, controller), numCopies);
|
||||
} // end foreach Card
|
||||
|
||||
for (final Card c : tgtCards) {
|
||||
// if it only targets player, it already got all needed cards from defined
|
||||
if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer() && !c.canBeTargetedBy(sa)) {
|
||||
continue;
|
||||
}
|
||||
if (sa.hasParam("ForEach")) {
|
||||
for (Player p : AbilityUtils.getDefinedPlayers(host, sa.getParam("ForEach"), sa)) {
|
||||
Card proto = getProtoType(sa, c, controller);
|
||||
proto.addRemembered(p);
|
||||
tokenTable.put(controller, proto, numCopies);
|
||||
}
|
||||
} else {
|
||||
tokenTable.put(controller, getProtoType(sa, c, controller), numCopies);
|
||||
}
|
||||
} // end foreach Card
|
||||
}
|
||||
|
||||
makeTokenTable(tokenTable, true, triggerList, combatChanged, sa);
|
||||
|
||||
@@ -243,7 +252,7 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
||||
}
|
||||
} // end resolve
|
||||
|
||||
private Card getProtoType(final SpellAbility sa, final Card original, final Player newOwner) {
|
||||
public static Card getProtoType(final SpellAbility sa, final Card original, final Player newOwner) {
|
||||
final Card host = sa.getHostCard();
|
||||
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
|
||||
final Card copy = new Card(id, original.getPaperCard(), host.getGame());
|
||||
|
||||
@@ -26,6 +26,7 @@ import forge.util.Aggregates;
|
||||
import forge.util.CardTranslation;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.Lang;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
|
||||
public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
@@ -128,32 +129,38 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
final Player p = (Player) o;
|
||||
if (p.equals(originalTargetPlayer))
|
||||
continue;
|
||||
if (p.isValid(type.split(","), chosenSA.getActivatingPlayer(), chosenSA.getHostCard(), sa)) {
|
||||
if (p.isValid(type.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa)) {
|
||||
players.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
valid = CardLists.getValidCards(valid, type, chosenSA.getActivatingPlayer(), chosenSA.getHostCard(), sa);
|
||||
valid = CardLists.getValidCards(valid, type, sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
Card originalTarget = Iterables.getFirst(getTargetCards(chosenSA), null);
|
||||
valid.remove(originalTarget);
|
||||
|
||||
if (sa.hasParam("ChooseOnlyOne")) {
|
||||
Card choice = controller.getController().chooseSingleEntityForEffect(valid, sa, Localizer.getInstance().getMessage("lblChooseOne"), null);
|
||||
FCollection<GameEntity> all = new FCollection<>(valid);
|
||||
all.addAll(players);
|
||||
GameEntity choice = controller.getController().chooseSingleEntityForEffect(all, sa,
|
||||
Localizer.getInstance().getMessage("lblChooseOne"), null);
|
||||
if (choice != null) {
|
||||
valid = new CardCollection(choice);
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
|
||||
resetFirstTargetOnCopy(copy, choice, targetedSA);
|
||||
copies.add(copy);
|
||||
}
|
||||
} else {
|
||||
for (final Card c : valid) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
|
||||
resetFirstTargetOnCopy(copy, c, targetedSA);
|
||||
copies.add(copy);
|
||||
}
|
||||
for (final Player p : players) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
|
||||
resetFirstTargetOnCopy(copy, p, targetedSA);
|
||||
copies.add(copy);
|
||||
}
|
||||
}
|
||||
|
||||
for (final Card c : valid) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
|
||||
resetFirstTargetOnCopy(copy, c, targetedSA);
|
||||
copies.add(copy);
|
||||
}
|
||||
for (final Player p : players) {
|
||||
SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
|
||||
resetFirstTargetOnCopy(copy, p, targetedSA);
|
||||
copies.add(copy);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < amount; i++) {
|
||||
|
||||
@@ -45,10 +45,10 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
final String countername = sa.getParam("CounterType");
|
||||
final String counterAmount = sa.getParam("CounterNum");
|
||||
final String counterAmount = sa.getParamOrDefault("CounterNum", "1");
|
||||
int amount = 0;
|
||||
if (!"Any".equals(counterAmount) && !"All".equals(counterAmount)) {
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CounterNum"), sa);
|
||||
amount = AbilityUtils.calculateAmount(sa.getHostCard(), counterAmount, sa);
|
||||
}
|
||||
|
||||
sb.append("Move ");
|
||||
@@ -81,7 +81,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
final String counterName = sa.getParam("CounterType");
|
||||
final String counterNum = sa.getParam("CounterNum");
|
||||
final String counterNum = sa.getParamOrDefault("CounterNum", "1");
|
||||
final Player player = sa.getActivatingPlayer();
|
||||
final PlayerController pc = player.getController();
|
||||
final Game game = host.getGame();
|
||||
@@ -320,7 +320,6 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
|
||||
protected void removeCounter(SpellAbility sa, final Card src, final Card dest, CounterType cType, String counterNum, Map<CounterType, Integer> countersToAdd) {
|
||||
final Card host = sa.getHostCard();
|
||||
//final String counterNum = sa.getParam("CounterNum");
|
||||
final Player player = sa.getActivatingPlayer();
|
||||
final PlayerController pc = player.getController();
|
||||
final Game game = host.getGame();
|
||||
|
||||
@@ -34,54 +34,85 @@ import forge.util.Localizer;
|
||||
|
||||
public class CountersPutEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility spellAbility) {
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
final Card card = spellAbility.getHostCard();
|
||||
final Card card = sa.getHostCard();
|
||||
final String who = sa.getActivatingPlayer().getName();
|
||||
boolean pronoun = false;
|
||||
|
||||
final int amount = AbilityUtils.calculateAmount(card,
|
||||
spellAbility.getParamOrDefault("CounterNum", "1"), spellAbility);
|
||||
if (spellAbility.hasParam("CounterTypes")) {
|
||||
stringBuilder.append(spellAbility.getActivatingPlayer()).append(" ");
|
||||
String desc = spellAbility.getDescription();
|
||||
if (desc.contains("Put")) {
|
||||
desc = desc.substring(desc.indexOf("Put"), desc.indexOf(" on ") + 4)
|
||||
.replaceFirst("Put", "puts");
|
||||
if (sa.hasParam("IfDesc")) {
|
||||
final String ifD = sa.getParam("IfDesc");
|
||||
if (ifD.equals("True")) {
|
||||
String ifDesc = sa.getDescription();
|
||||
if (ifDesc.contains(",")) {
|
||||
if (ifDesc.contains(" you ")) {
|
||||
ifDesc = ifDesc.replaceFirst(" you ", " " + who + " ");
|
||||
pronoun = true;
|
||||
if (ifDesc.contains(" you ")) {
|
||||
ifDesc = ifDesc.replaceAll(" you ", " they ");
|
||||
}
|
||||
if (ifDesc.contains(" your ")) {
|
||||
ifDesc = ifDesc.replaceAll(" your ", " their ");
|
||||
}
|
||||
}
|
||||
stringBuilder.append(ifDesc, 0, ifDesc.indexOf(",") + 1);
|
||||
} else {
|
||||
stringBuilder.append("[CountersPutEffect IfDesc parsing error]");
|
||||
}
|
||||
} else {
|
||||
stringBuilder.append(ifD);
|
||||
}
|
||||
stringBuilder.append(desc).append(Lang.joinHomogenous(getTargets(spellAbility))).append(".");
|
||||
stringBuilder.append(" ");
|
||||
}
|
||||
|
||||
stringBuilder.append(pronoun ? "they" : who).append(" ");
|
||||
|
||||
if (sa.hasParam("CounterTypes")) {
|
||||
String desc = sa.getDescription();
|
||||
if (desc.contains("Put ") && desc.contains(" on ")) {
|
||||
desc = desc.substring(desc.indexOf("Put "), desc.indexOf(" on ") + 4)
|
||||
.replaceFirst("Put ", "puts ");
|
||||
}
|
||||
stringBuilder.append(desc).append(Lang.joinHomogenous(getTargets(sa))).append(".");
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
// skip the StringBuilder if no targets are chosen ("up to" scenario)
|
||||
if (spellAbility.usesTargeting()) {
|
||||
final List<Card> targetCards = SpellAbilityEffect.getTargetCards(spellAbility);
|
||||
if (sa.usesTargeting()) {
|
||||
final List<Card> targetCards = SpellAbilityEffect.getTargetCards(sa);
|
||||
if (targetCards.size() == 0) {
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
}
|
||||
if (spellAbility.hasParam("Bolster")) {
|
||||
stringBuilder.append("Bolster ").append(amount);
|
||||
|
||||
final int amount = AbilityUtils.calculateAmount(card,
|
||||
sa.getParamOrDefault("CounterNum", "1"), sa);
|
||||
|
||||
if (sa.hasParam("Bolster")) {
|
||||
stringBuilder.append("bolsters ").append(amount).append(".");
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
boolean divAsChoose = spellAbility.isDividedAsYouChoose();
|
||||
boolean divAsChoose = sa.isDividedAsYouChoose();
|
||||
final boolean divRandom = sa.hasParam("DividedRandomly");
|
||||
if (divAsChoose) {
|
||||
stringBuilder.append("Distribute ");
|
||||
} else if (spellAbility.hasParam("DividedRandomly")) {
|
||||
stringBuilder.append("Randomly distribute ");
|
||||
stringBuilder.append(pronoun ? "distribute " : "distributes ");
|
||||
} else if (divRandom) {
|
||||
stringBuilder.append(pronoun ? "randomly distribute " : "randomly distributes ");
|
||||
} else {
|
||||
stringBuilder.append("Put ");
|
||||
stringBuilder.append(pronoun ? "put " : "puts ");
|
||||
}
|
||||
if (spellAbility.hasParam("UpTo")) {
|
||||
if (sa.hasParam("UpTo")) {
|
||||
stringBuilder.append("up to ");
|
||||
}
|
||||
|
||||
final String typeName = CounterType.getType(spellAbility.getParam("CounterType")).getName().toLowerCase();
|
||||
final String typeName = CounterType.getType(sa.getParam("CounterType")).getName().toLowerCase();
|
||||
stringBuilder.append(Lang.nounWithNumeralExceptOne(amount, typeName + " counter"));
|
||||
stringBuilder.append(divAsChoose || spellAbility.hasParam("DividedRandomly") ? " among " : " on ");
|
||||
stringBuilder.append(divAsChoose || divRandom ? " among " : " on ");
|
||||
|
||||
// special handling for multiple Defined
|
||||
if (spellAbility.hasParam("Defined") && spellAbility.getParam("Defined").contains(" & ")) {
|
||||
String[] def = spellAbility.getParam("Defined").split(" & ");
|
||||
if (sa.hasParam("Defined") && sa.getParam("Defined").contains(" & ")) {
|
||||
String[] def = sa.getParam("Defined").split(" & ");
|
||||
for (int i = 0; i < def.length; i++) {
|
||||
stringBuilder.append(AbilityUtils.getDefinedEntities(card, def[i], spellAbility).toString()
|
||||
stringBuilder.append(AbilityUtils.getDefinedEntities(card, def[i], sa).toString()
|
||||
.replaceAll("[\\[\\]]", ""));
|
||||
if (i + 1 < def.length) {
|
||||
stringBuilder.append(" and ");
|
||||
@@ -89,12 +120,12 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
// if use targeting we show all targets and corresponding counters
|
||||
} else if (spellAbility.usesTargeting()) {
|
||||
final List<Card> targetCards = SpellAbilityEffect.getTargetCards(spellAbility);
|
||||
} else if (sa.usesTargeting()) {
|
||||
final List<Card> targetCards = SpellAbilityEffect.getTargetCards(sa);
|
||||
for (int i = 0; i < targetCards.size(); i++) {
|
||||
Card targetCard = targetCards.get(i);
|
||||
stringBuilder.append(targetCard);
|
||||
Integer v = spellAbility.getDividedValue(targetCard);
|
||||
Integer v = sa.getDividedValue(targetCard);
|
||||
if (v != null) // fix null counter stack description
|
||||
stringBuilder.append(" (").append(v).append(v == 1 ? " counter)" : " counters)");
|
||||
|
||||
@@ -104,8 +135,12 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
stringBuilder.append(", ");
|
||||
}
|
||||
}
|
||||
} else if (sa.hasParam("Choices")) {
|
||||
int n = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("ChoiceAmount", "1"), sa);
|
||||
String what = (sa.getParamOrDefault("ChoicesDesc", sa.getParam("Choices")));
|
||||
stringBuilder.append(Lang.nounWithNumeralExceptOne(n, what));
|
||||
} else {
|
||||
final List<Card> targetCards = SpellAbilityEffect.getTargetCards(spellAbility);
|
||||
final List<Card> targetCards = SpellAbilityEffect.getTargetCards(sa);
|
||||
final Iterator<Card> it = targetCards.iterator();
|
||||
while (it.hasNext()) {
|
||||
final Card targetCard = it.next();
|
||||
@@ -135,6 +170,8 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
|
||||
boolean existingCounter = sa.hasParam("CounterType") && sa.getParam("CounterType").equals("ExistingCounter");
|
||||
boolean eachExistingCounter = sa.hasParam("EachExistingCounter");
|
||||
boolean putOnEachOther = sa.hasParam("PutOnEachOther");
|
||||
boolean putOnDefined = sa.hasParam("PutOnDefined");
|
||||
|
||||
if (sa.hasParam("Optional") && !pc.confirmAction
|
||||
(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPutCounter"))) {
|
||||
@@ -146,14 +183,14 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("Bolster")) {
|
||||
CardCollection creatsYouCtrl = activator.getCreaturesInPlay();
|
||||
CardCollection leastToughness = new CardCollection(
|
||||
Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetDefense));
|
||||
Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetNetToughness));
|
||||
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("CounterType", counterType);
|
||||
|
||||
Iterables.addAll(tgtObjects, activator.getController().chooseCardsForEffect(leastToughness, sa,
|
||||
Localizer.getInstance().getMessage("lblChooseACreatureWithLeastToughness"), 1, 1, false, params));
|
||||
} else if (sa.hasParam("Choices") && counterType != null) {
|
||||
} else if (sa.hasParam("Choices") && (counterType != null || putOnEachOther || putOnDefined)) {
|
||||
ZoneType choiceZone = ZoneType.Battlefield;
|
||||
if (sa.hasParam("ChoiceZone")) {
|
||||
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
|
||||
@@ -186,6 +223,11 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
}
|
||||
if ((sa.hasParam("ChoiceTitle") || sa.hasParam("SpecifyCounter")) && counterType != null) {
|
||||
title += " (" + counterType.getName() + ")";
|
||||
} else if (putOnEachOther || putOnDefined) {
|
||||
title += Localizer.getInstance().getMessage("lblWithKindCounter");
|
||||
if (putOnEachOther) {
|
||||
title += " " + Localizer.getInstance().getMessage("lblEachOther");
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
@@ -298,6 +340,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
for (CounterType ct : counterTypes) {
|
||||
if (sa.hasParam("AltChoiceForEach")) {
|
||||
String typeChoices = sa.getParam("AltChoiceForEach") + "," + ct.toString();
|
||||
ct = chooseTypeFromList(sa, typeChoices, obj, pc);
|
||||
}
|
||||
resolvePerType(sa, placer, ct, counterAmount, table, false);
|
||||
}
|
||||
} else {
|
||||
@@ -309,7 +355,11 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
((Player) obj).addCounter(ct, counterAmount, placer, table);
|
||||
}
|
||||
if (obj instanceof Card) {
|
||||
gameCard.addCounter(ct, counterAmount, placer, table);
|
||||
if (etbcounter) {
|
||||
gameCard.addEtbCounter(ct, counterAmount, placer);
|
||||
} else {
|
||||
gameCard.addCounter(ct, counterAmount, placer, table);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
@@ -319,7 +369,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
final List<CounterType> choices = Lists.newArrayList();
|
||||
// get types of counters
|
||||
for (CounterType ct : obj.getCounters().keySet()) {
|
||||
if (obj.canReceiveCounters(ct)) {
|
||||
if (obj.canReceiveCounters(ct) || putOnEachOther) {
|
||||
choices.add(ct);
|
||||
}
|
||||
}
|
||||
@@ -343,10 +393,30 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
} else {
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Target", obj);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(Localizer.getInstance().getMessage("lblSelectCounterTypeAddTo") + " ");
|
||||
sb.append(obj);
|
||||
counterType = pc.chooseCounterType(choices, sa, sb.toString(), params);
|
||||
String sb = Localizer.getInstance().getMessage("lblSelectCounterTypeAddTo") +
|
||||
" " + (putOnEachOther ? Localizer.getInstance().getMessage("lblEachOther") : obj);
|
||||
counterType = pc.chooseCounterType(choices, sa, sb, params);
|
||||
}
|
||||
if (putOnEachOther) {
|
||||
List<Card> others = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield),
|
||||
sa.getParam("PutOnEachOther"), activator, card, sa);
|
||||
for (Card other : others) {
|
||||
if (other.equals(obj)) {
|
||||
continue;
|
||||
}
|
||||
Card otherGCard = game.getCardState(other, null);
|
||||
otherGCard.addCounter(counterType, counterAmount, placer, table);
|
||||
}
|
||||
continue;
|
||||
} else if (putOnDefined) {
|
||||
List<Card> defs = AbilityUtils.getDefinedCards(card, sa.getParam("PutOnDefined"), sa);
|
||||
for (Card c : defs) {
|
||||
Card gCard = game.getCardState(c, null);
|
||||
if (!sa.hasParam("OnlyNewKind") || gCard.getCounters(counterType) < 1) {
|
||||
gCard.addCounter(counterType, counterAmount, placer, table);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,6 +432,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
}
|
||||
if (sa.hasParam("CounterTypePerDefined") || sa.hasParam("UniqueType")) {
|
||||
counterType = chooseTypeFromList(sa, sa.getParam("CounterType"), obj, pc);
|
||||
if (counterType == null) continue;
|
||||
}
|
||||
|
||||
if (obj instanceof Card) {
|
||||
@@ -528,7 +599,8 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
CounterType counterType = null;
|
||||
if (!sa.hasParam("EachExistingCounter") && !sa.hasParam("EachFromSource")
|
||||
&& !sa.hasParam("UniqueType") && !sa.hasParam("CounterTypePerDefined")
|
||||
&& !sa.hasParam("CounterTypes") && !sa.hasParam("ChooseDifferent")) {
|
||||
&& !sa.hasParam("CounterTypes") && !sa.hasParam("ChooseDifferent")
|
||||
&& !sa.hasParam("PutOnEachOther") && !sa.hasParam("PutOnDefined")) {
|
||||
try {
|
||||
counterType = chooseTypeFromList(sa, sa.getParam("CounterType"), null,
|
||||
placer.getController());
|
||||
@@ -596,7 +668,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
List<CounterType> choices = Lists.newArrayList();
|
||||
for (String s : list.split(",")) {
|
||||
if (!s.equals("") && (!sa.hasParam("UniqueType") || obj.getCounters(CounterType.getType(s)) == 0)) {
|
||||
choices.add(CounterType.getType(s));
|
||||
CounterType type = CounterType.getType(s);
|
||||
if (!choices.contains(type)) {
|
||||
choices.add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("RandomType")) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.game.ability.effects;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.game.card.*;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -12,10 +13,6 @@ import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -29,7 +26,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
final String counterName = sa.getParam("CounterType");
|
||||
final String num = sa.getParam("CounterNum");
|
||||
final String num = sa.getParamOrDefault("CounterNum", "1");
|
||||
|
||||
int amount = 0;
|
||||
if (!num.equals("All") && !num.equals("Any")) {
|
||||
@@ -77,7 +74,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
|
||||
PlayerController pc = player.getController();
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String num = sa.getParam("CounterNum");
|
||||
final String num = sa.getParamOrDefault("CounterNum", "1");
|
||||
|
||||
int cntToRemove = 0;
|
||||
if (!num.equals("All") && !num.equals("Any")) {
|
||||
@@ -126,15 +123,29 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
CardCollectionView srcCards = null;
|
||||
|
||||
String typeforPrompt = counterType == null ? "" : counterType.getName();
|
||||
String title = Localizer.getInstance().getMessage("lblChooseCardsToTakeTargetCounters", typeforPrompt);
|
||||
title = title.replace(" ", " ");
|
||||
if (sa.hasParam("ValidSource")) {
|
||||
srcCards = game.getCardsIn(ZoneType.Battlefield);
|
||||
srcCards = CardLists.getValidCards(srcCards, sa.getParam("ValidSource"), player, card, sa);
|
||||
if (num.equals("Any")) {
|
||||
String title = Localizer.getInstance().getMessage("lblChooseCardsToTakeTargetCounters", counterType.getName());
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("CounterType", counterType);
|
||||
srcCards = player.getController().chooseCardsForEffect(srcCards, sa, title, 0, srcCards.size(), true, params);
|
||||
}
|
||||
} else if (sa.hasParam("Choices") && counterType != null) {
|
||||
ZoneType choiceZone = sa.hasParam("ChoiceZone") ? ZoneType.smartValueOf(sa.getParam("ChoiceZone"))
|
||||
: ZoneType.Battlefield;
|
||||
|
||||
CardCollection choices = CardLists.getValidCards(game.getCardsIn(choiceZone), sa.getParam("Choices"),
|
||||
player, card, sa);
|
||||
|
||||
//currently only used by one card, so for now
|
||||
//amount is locked at 1 and choice is mandatory
|
||||
srcCards = pc.chooseCardsForEffect(choices, sa, title, 1, 1,
|
||||
false, null);
|
||||
} else {
|
||||
srcCards = getTargetCards(sa);
|
||||
}
|
||||
@@ -171,7 +182,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Target", gameCard);
|
||||
params.put("CounterType", counterType);
|
||||
String title = Localizer.getInstance().getMessage("lblSelectRemoveCountersNumberOfTarget", type);
|
||||
title = Localizer.getInstance().getMessage("lblSelectRemoveCountersNumberOfTarget", type);
|
||||
cntToRemove = pc.chooseNumber(sa, title, 0, cntToRemove, params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,17 @@ public class DelayedTriggerEffect extends SpellAbilityEffect {
|
||||
}
|
||||
};
|
||||
game.getCleanup().addUntil(nextTurnTrig);
|
||||
} else if (mapParams.containsKey("UpcomingTurn")) {
|
||||
final GameCommand upcomingTurnTrig = new GameCommand() {
|
||||
private static final long serialVersionUID = -5860518814760461373L;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
trigHandler.registerDelayedTrigger(delTrig);
|
||||
|
||||
}
|
||||
};
|
||||
game.getCleanup().addUntil(upcomingTurnTrig);
|
||||
} else {
|
||||
trigHandler.registerDelayedTrigger(delTrig);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import forge.util.Lang;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class DigEffect extends SpellAbilityEffect {
|
||||
|
||||
@@ -46,7 +47,7 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
verb = " exiles ";
|
||||
}
|
||||
sb.append(host.getController()).append(verb).append("the top ");
|
||||
sb.append(numToDig == 1 ? "card" : (Lang.getNumeral(numToDig) + " cards")).append(" of ");
|
||||
sb.append(numToDig == 1 ? "card" : Lang.getNumeral(numToDig) + " cards").append(" of ");
|
||||
|
||||
if (tgtPlayers.contains(host.getController())) {
|
||||
sb.append("their ");
|
||||
@@ -67,7 +68,7 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
String verb2 = "put ";
|
||||
String where = " in their hand ";
|
||||
String where = " into their hand ";
|
||||
if (destZone1.equals("exile")) {
|
||||
verb2 = "exile ";
|
||||
where = " ";
|
||||
@@ -80,6 +81,9 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("ChangeValid")) {
|
||||
String what = sa.hasParam("ChangeValidDesc") ? sa.getParam("ChangeValidDesc") :
|
||||
sa.getParam("ChangeValid");
|
||||
if (!StringUtils.containsIgnoreCase(what, "card")) {
|
||||
what = what + " card";
|
||||
}
|
||||
sb.append(Lang.nounWithNumeralExceptOne(numToChange, what)).append(" from among them").append(where);
|
||||
} else {
|
||||
sb.append(Lang.getNumeral(numToChange)).append(" of them").append(where);
|
||||
@@ -420,6 +424,7 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("ExileWithCounter")) {
|
||||
c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")), 1, player, counterTable);
|
||||
}
|
||||
effectHost.addExiledCard(c);
|
||||
c.setExiledWith(effectHost);
|
||||
c.setExiledBy(effectHost.getController());
|
||||
}
|
||||
@@ -492,6 +497,7 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("ExileWithCounter")) {
|
||||
c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")), 1, player, counterTable);
|
||||
}
|
||||
effectHost.addExiledCard(c);
|
||||
c.setExiledWith(effectHost);
|
||||
c.setExiledBy(effectHost.getController());
|
||||
if (remZone2) {
|
||||
|
||||
@@ -64,7 +64,7 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("DiscardValid")) {
|
||||
String validD = sa.hasParam("DiscardValidDesc") ? sa.getParam("DiscardValidDesc")
|
||||
: sa.getParam("DiscardValid");
|
||||
if (validD.equals("card.nonLand")) {
|
||||
if (validD.equals("Card.nonLand")) {
|
||||
validD = "nonland";
|
||||
} else if (CardType.CoreType.isValidEnum(validD)) {
|
||||
validD = validD.toLowerCase();
|
||||
|
||||
@@ -36,9 +36,7 @@ public class DrainManaEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (sa.hasParam("DrainMana")) {
|
||||
for (Mana mana : drained) {
|
||||
sa.getActivatingPlayer().getManaPool().addMana(mana);
|
||||
}
|
||||
sa.getActivatingPlayer().getManaPool().add(drained);
|
||||
}
|
||||
if (sa.hasParam("RememberDrainedMana")) {
|
||||
sa.getHostCard().addRemembered(Integer.valueOf(drained.size()));
|
||||
|
||||
@@ -40,7 +40,10 @@ public class DrawEffect extends SpellAbilityEffect {
|
||||
sb.append(" each");
|
||||
}
|
||||
sb.append(Lang.joinVerb(tgtPlayers, " draw")).append(" ");
|
||||
sb.append(numCards == 1 ? "a card" : (Lang.getNumeral(numCards) + " cards"));
|
||||
//if NumCards calculation could change between getStackDescription and resolve, use NumCardsDesc to avoid
|
||||
//a "wrong" stack description
|
||||
sb.append(sa.hasParam("NumCardsDesc") ? sa.getParam("NumCardsDesc") : numCards == 1 ? "a card" :
|
||||
(Lang.getNumeral(numCards) + " cards"));
|
||||
sb.append(".");
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ public class EffectEffect extends SpellAbilityEffect {
|
||||
boolean imprintOnHost = false;
|
||||
final String duration = sa.getParam("Duration");
|
||||
|
||||
if (("UntilHostLeavesPlay".equals(duration) || "UntilLoseControlOfHost".equals(duration))
|
||||
if (((duration != null && duration.startsWith("UntilHostLeavesPlay")) || "UntilLoseControlOfHost".equals(duration))
|
||||
&& !(hostCard.isInPlay() || hostCard.isInZone(ZoneType.Stack))) {
|
||||
return;
|
||||
}
|
||||
@@ -271,7 +271,7 @@ public class EffectEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (sa.hasParam("CopySVar")) {
|
||||
eff.setSVar(sa.getParam("CopySVar"), sa.getHostCard().getSVar(sa.getParam("CopySVar")));
|
||||
eff.setSVar(sa.getParam("CopySVar"), hostCard.getSVar(sa.getParam("CopySVar")));
|
||||
}
|
||||
|
||||
// Copy text changes
|
||||
|
||||
@@ -20,7 +20,7 @@ public class EncodeEffect extends SpellAbilityEffect {
|
||||
if (sa.getHostCard().isToken()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(sa.getActivatingPlayer());
|
||||
@@ -73,8 +73,6 @@ public class EncodeEffect extends SpellAbilityEffect {
|
||||
// store hostcard in encoded array
|
||||
choice.addEncodedCard(movedCard);
|
||||
movedCard.setEncodingCard(choice);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -165,7 +165,9 @@ public class FightEffect extends DamageBaseEffect {
|
||||
|
||||
damageMap.put(fighterA, fighterB, dmg1);
|
||||
damageMap.put(fighterB, fighterA, dmg2);
|
||||
fighterB.setFoughtThisTurn(true);
|
||||
}
|
||||
fighterA.setFoughtThisTurn(true);
|
||||
|
||||
if (!usedDamageMap) {
|
||||
sa.getHostCard().getGame().getAction().dealDamage(false, damageMap, preventMap, counterTable, sa);
|
||||
|
||||
@@ -171,8 +171,6 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AllZone.getTriggerHandler().runTrigger("FlipsACoin",runParams);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,12 +18,12 @@ public class GoadEffect extends SpellAbilityEffect {
|
||||
final boolean remember = sa.hasParam("RememberGoaded");
|
||||
|
||||
for (final Card tgtC : getDefinedCardsOrTargeted(sa)) {
|
||||
// only pump things in PumpZone
|
||||
// only goad things on the battlefield
|
||||
if (!game.getCardsIn(ZoneType.Battlefield).contains(tgtC)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if pump is a target, make sure we can still target now
|
||||
// make sure we can still target now if using targeting
|
||||
if (sa.usesTargeting() && !sa.getTargetRestrictions().canTgtPlayer() && !tgtC.canBeTargetedBy(sa)) {
|
||||
continue;
|
||||
}
|
||||
@@ -31,16 +31,19 @@ public class GoadEffect extends SpellAbilityEffect {
|
||||
// 701.38d is handled by getGoaded
|
||||
tgtC.addGoad(timestamp, player);
|
||||
|
||||
final GameCommand untilEOT = new GameCommand() {
|
||||
private static final long serialVersionUID = -1731759226844770852L;
|
||||
// currently, only Life of the Party uses Duration$ – Duration$ Permanent
|
||||
if (!sa.hasParam("Duration")) {
|
||||
final GameCommand untilEOT = new GameCommand() {
|
||||
private static final long serialVersionUID = -1731759226844770852L;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
tgtC.removeGoad(timestamp);
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public void run() {
|
||||
tgtC.removeGoad(timestamp);
|
||||
}
|
||||
};
|
||||
|
||||
game.getCleanup().addUntil(player, untilEOT);
|
||||
game.getCleanup().addUntil(player, untilEOT);
|
||||
}
|
||||
|
||||
if (remember && tgtC.isGoaded()) {
|
||||
sa.getHostCard().addRemembered(tgtC);
|
||||
|
||||
@@ -245,8 +245,6 @@ public class ManaEffect extends SpellAbilityEffect {
|
||||
|
||||
// Only clear express choice after mana has been produced
|
||||
abMana.clearExpressChoice();
|
||||
|
||||
//resolveDrawback(sa);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,9 +268,17 @@ public class ManaEffect extends SpellAbilityEffect {
|
||||
? GameActionUtil.generatedMana(sa) : "mana";
|
||||
sb.append("Add ").append(toManaString(mana)).append(".");
|
||||
if (sa.hasParam("RestrictValid")) {
|
||||
String desc = sa.getDescription();
|
||||
int i = desc.indexOf("Spend this");
|
||||
sb.append(" ").append(desc, i, desc.indexOf(".", i) + 1);
|
||||
sb.append(" ");
|
||||
final String desc = sa.getDescription();
|
||||
if (desc.contains("Spend this") && desc.contains(".")) {
|
||||
int i = desc.indexOf("Spend this");
|
||||
sb.append(desc, i, desc.indexOf(".", i) + 1);
|
||||
} else if (desc.contains("This mana can't") && desc.contains(".")) { //for negative restrictions (Jegantha)
|
||||
int i = desc.indexOf("This mana can't");
|
||||
sb.append(desc, i, desc.indexOf(".", i) + 1);
|
||||
} else {
|
||||
sb.append("[failed to add RestrictValid to StackDesc]");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class ManaReflectedEffect extends SpellAbilityEffect {
|
||||
public void resolve(SpellAbility sa) {
|
||||
// Spells are not undoable
|
||||
AbilityManaPart ma = sa.getManaPart();
|
||||
sa.setUndoable(sa.isAbility() && sa.isUndoable());
|
||||
sa.setUndoable(sa.isAbility() && sa.isUndoable() && sa.getSubAbility() == null);
|
||||
|
||||
final Collection<String> colors = CardUtil.getReflectableManaColors(sa);
|
||||
|
||||
@@ -31,8 +31,6 @@ public class ManaReflectedEffect extends SpellAbilityEffect {
|
||||
final String generated = generatedReflectedMana(sa, colors, player);
|
||||
ma.produceMana(generated, player, sa);
|
||||
}
|
||||
|
||||
resolveSubAbility(sa);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Lang;
|
||||
@@ -68,6 +69,7 @@ public class MillEffect extends SpellAbilityEffect {
|
||||
host = sa.getHostCard();
|
||||
}
|
||||
for (final Card c : milled) {
|
||||
host.addExiledCard(c);
|
||||
c.setExiledWith(host);
|
||||
if (facedown) {
|
||||
c.turnFaceDown(true);
|
||||
@@ -91,17 +93,40 @@ public class MillEffect extends SpellAbilityEffect {
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final int numCards = sa.hasParam("NumCards") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) : 1;
|
||||
final boolean optional = sa.hasParam("Optional");
|
||||
final boolean eachP = sa.hasParam("Defined") && sa.getParam("Defined").equals("Player");
|
||||
String each = "Each player";
|
||||
final PlayerCollection tgtPs = getTargetPlayers(sa);
|
||||
|
||||
sb.append(Lang.joinHomogenous(getTargetPlayers(sa))).append(" ");
|
||||
if (sa.hasParam("IfDesc")) {
|
||||
final String ifD = sa.getParam("IfDesc");
|
||||
if (ifD.equals("True")) {
|
||||
String ifDesc = sa.getDescription();
|
||||
if (ifDesc.contains(",")) {
|
||||
sb.append(ifDesc, 0, ifDesc.indexOf(",") + 1);
|
||||
} else {
|
||||
sb.append("[MillEffect IfDesc parsing error]");
|
||||
}
|
||||
} else {
|
||||
sb.append(ifD);
|
||||
}
|
||||
sb.append(" ");
|
||||
each = each.toLowerCase();
|
||||
}
|
||||
|
||||
sb.append(eachP ? each : Lang.joinHomogenous(tgtPs));
|
||||
sb.append(" ");
|
||||
|
||||
final ZoneType dest = ZoneType.smartValueOf(sa.getParam("Destination"));
|
||||
sb.append(optional ? "may " : "");
|
||||
if ((dest == null) || dest.equals(ZoneType.Graveyard)) {
|
||||
sb.append("mills ");
|
||||
sb.append("mill");
|
||||
} else if (dest.equals(ZoneType.Exile)) {
|
||||
sb.append("exiles ");
|
||||
sb.append("exile");
|
||||
} else if (dest.equals(ZoneType.Ante)) {
|
||||
sb.append("antes ");
|
||||
sb.append("ante");
|
||||
}
|
||||
sb.append((optional || tgtPs.size() > 1) && !eachP ? " " : "s ");
|
||||
|
||||
sb.append(Lang.nounWithNumeralExceptOne(numCards, "card")).append(".");
|
||||
|
||||
|
||||
@@ -29,21 +29,23 @@ public class PeekAndRevealEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final Player peeker = sa.getActivatingPlayer();
|
||||
|
||||
final int numPeek = sa.hasParam("PeekAmount") ?
|
||||
AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("PeekAmount"), sa) : 1;
|
||||
final String verb = sa.hasParam("NoReveal") || sa.hasParam("RevealOptional") ? " looks at " :
|
||||
" reveals ";
|
||||
final String defined = sa.getParamOrDefault("Defined", "their");
|
||||
String whose;
|
||||
if (defined.equals("Player")) {
|
||||
whose = "each player's";
|
||||
} else { // other else ifs for specific defined can be added above as needs arise
|
||||
whose = Lang.joinHomogenous(getTargetPlayers(sa));
|
||||
}
|
||||
final String defined = sa.getParamOrDefault("Defined", "");
|
||||
final List<Player> libraryPlayers = getDefinedPlayersOrTargeted(sa);
|
||||
final String defString = Lang.joinHomogenous(libraryPlayers);
|
||||
String who = defined.equals("Player") && verb.equals(" reveals ") ? "Each player" :
|
||||
sa.hasParam("NoPeek") && verb.equals(" reveals ") ? defString : "";
|
||||
String whose = defined.equals("Player") && verb.equals(" looks at ") ? "each player's"
|
||||
: libraryPlayers.size() == 1 && libraryPlayers.get(0) == peeker ? "their" :
|
||||
defString + "'s";
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(peeker).append(verb).append("the top ");
|
||||
sb.append(who.equals("") ? peeker : who);
|
||||
sb.append(verb).append("the top ");
|
||||
sb.append(numPeek > 1 ? Lang.getNumeral(numPeek) + " cards " : "card ").append("of ").append(whose);
|
||||
sb.append(" library.");
|
||||
|
||||
@@ -58,11 +60,12 @@ public class PeekAndRevealEffect extends SpellAbilityEffect {
|
||||
final Card source = sa.getHostCard();
|
||||
final boolean rememberRevealed = sa.hasParam("RememberRevealed");
|
||||
final boolean imprintRevealed = sa.hasParam("ImprintRevealed");
|
||||
final boolean noPeek = sa.hasParam("NoPeek");
|
||||
String revealValid = sa.getParamOrDefault("RevealValid", "Card");
|
||||
String peekAmount = sa.getParamOrDefault("PeekAmount", "1");
|
||||
int numPeek = AbilityUtils.calculateAmount(source, peekAmount, sa);
|
||||
|
||||
List<Player> libraryPlayers = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
List<Player> libraryPlayers = getDefinedPlayersOrTargeted(sa);
|
||||
Player peekingPlayer = sa.getActivatingPlayer();
|
||||
|
||||
for (Player libraryToPeek : libraryPlayers) {
|
||||
@@ -77,17 +80,19 @@ public class PeekAndRevealEffect extends SpellAbilityEffect {
|
||||
CardCollectionView revealableCards = CardLists.getValidCards(peekCards, revealValid,
|
||||
sa.getActivatingPlayer(), source, sa);
|
||||
boolean doReveal = !sa.hasParam("NoReveal") && !revealableCards.isEmpty();
|
||||
if (!sa.hasParam("NoPeek")) {
|
||||
if (!noPeek) {
|
||||
peekingPlayer.getController().reveal(peekCards, ZoneType.Library, libraryToPeek,
|
||||
CardTranslation.getTranslatedName(source.getName()) + " - " +
|
||||
Localizer.getInstance().getMessage("lblRevealingCardFrom"));
|
||||
Localizer.getInstance().getMessage("lblLookingCardFrom"));
|
||||
}
|
||||
|
||||
if (doReveal && sa.hasParam("RevealOptional"))
|
||||
doReveal = peekingPlayer.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblRevealCardToOtherPlayers"));
|
||||
|
||||
if (doReveal) {
|
||||
peekingPlayer.getGame().getAction().reveal(revealableCards, peekingPlayer);
|
||||
peekingPlayer.getGame().getAction().reveal(revealableCards, ZoneType.Library, libraryToPeek, !noPeek,
|
||||
CardTranslation.getTranslatedName(source.getName()) + " - " +
|
||||
Localizer.getInstance().getMessage("lblRevealingCardFrom"));
|
||||
|
||||
if (rememberRevealed) {
|
||||
Map<Integer, Card> cachedMap = Maps.newHashMap();
|
||||
|
||||
@@ -9,8 +9,10 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Localizer;
|
||||
|
||||
public class PhasesEffect extends SpellAbilityEffect {
|
||||
|
||||
@@ -36,7 +38,8 @@ public class PhasesEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
CardCollectionView tgtCards;
|
||||
final Game game = sa.getActivatingPlayer().getGame();
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
final Game game = activator.getGame();
|
||||
final Card source = sa.getHostCard();
|
||||
final boolean phaseInOrOut = sa.hasParam("PhaseInOrOut");
|
||||
final boolean wontPhaseInNormal = sa.hasParam("WontPhaseInNormal");
|
||||
@@ -53,6 +56,11 @@ public class PhasesEffect extends SpellAbilityEffect {
|
||||
} else {
|
||||
tgtCards = getTargetCards(sa);
|
||||
}
|
||||
if (sa.hasParam("AnyNumber")) {
|
||||
tgtCards = activator.getController().chooseCardsForEffect(tgtCards, sa,
|
||||
Localizer.getInstance().getMessage("lblChooseAnyNumberToPhase"),
|
||||
0, tgtCards.size(), true, null);
|
||||
}
|
||||
if (phaseInOrOut) { // Time and Tide and Oubliette
|
||||
for (final Card tgtC : tgtCards) {
|
||||
tgtC.phase(false);
|
||||
|
||||
@@ -25,6 +25,7 @@ import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostDiscard;
|
||||
import forge.game.cost.CostPart;
|
||||
@@ -86,9 +87,11 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
long controlledByTimeStamp = -1;
|
||||
final Game game = activator.getGame();
|
||||
boolean optional = sa.hasParam("Optional");
|
||||
boolean remember = sa.hasParam("RememberPlayed");
|
||||
final boolean remember = sa.hasParam("RememberPlayed");
|
||||
final boolean imprint = sa.hasParam("ImprintPlayed");
|
||||
final boolean forget = sa.hasParam("ForgetPlayed");
|
||||
final boolean hasTotalCMCLimit = sa.hasParam("WithTotalCMC");
|
||||
int amount = 1;
|
||||
boolean hasTotalCMCLimit = sa.hasParam("WithTotalCMC");
|
||||
int totalCMCLimit = Integer.MAX_VALUE;
|
||||
if (sa.hasParam("Amount") && !sa.getParam("Amount").equals("All")) {
|
||||
amount = AbilityUtils.calculateAmount(source, sa.getParam("Amount"), sa);
|
||||
@@ -319,6 +322,9 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
|
||||
final CardZoneTable triggerList = new CardZoneTable();
|
||||
final Zone originZone = tgtCard.getZone();
|
||||
|
||||
// lands will be played
|
||||
if (tgtSA instanceof LandAbility) {
|
||||
tgtSA.resolve();
|
||||
@@ -326,6 +332,20 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
if (remember) {
|
||||
source.addRemembered(tgtCard);
|
||||
}
|
||||
if (imprint) {
|
||||
source.addImprintedCard(tgtCard);
|
||||
}
|
||||
//Forget only if playing was successful
|
||||
if (forget) {
|
||||
source.removeRemembered(tgtCard);
|
||||
}
|
||||
|
||||
final Zone currentZone = game.getCardState(tgtCard).getZone();
|
||||
if (!originZone.equals(currentZone)) {
|
||||
triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), game.getCardState(tgtCard));
|
||||
}
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -399,18 +419,26 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (controller.getController().playSaFromPlayEffect(tgtSA)) {
|
||||
final Card played = tgtSA.getHostCard();
|
||||
if (remember) {
|
||||
source.addRemembered(tgtSA.getHostCard());
|
||||
source.addRemembered(played);
|
||||
}
|
||||
if (imprint) {
|
||||
source.addImprintedCard(played);
|
||||
}
|
||||
|
||||
//Forgot only if playing was successful
|
||||
if (sa.hasParam("ForgetRemembered")) {
|
||||
source.clearRemembered();
|
||||
}
|
||||
|
||||
if (sa.hasParam("ForgetTargetRemembered")) {
|
||||
if (forget) {
|
||||
source.removeRemembered(tgtCard);
|
||||
}
|
||||
|
||||
final Zone currentZone = game.getCardState(tgtCard).getZone();
|
||||
if (!originZone.equals(currentZone)) {
|
||||
triggerList.put(originZone.getZoneType(), currentZone.getZoneType(), game.getCardState(tgtCard));
|
||||
}
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
}
|
||||
|
||||
amount--;
|
||||
|
||||
@@ -37,13 +37,15 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
final long timestamp) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
final String duration = sa.getParam("Duration");
|
||||
|
||||
//if host is not on the battlefield don't apply
|
||||
// Suspend should does Affect the Stack
|
||||
if (("UntilHostLeavesPlay".equals(sa.getParam("Duration")) || "UntilLoseControlOfHost".equals(sa.getParam("Duration")))
|
||||
if (((duration != null && duration.startsWith("UntilHostLeavesPlay")) || "UntilLoseControlOfHost".equals(duration))
|
||||
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
|
||||
return;
|
||||
}
|
||||
if ("UntilLoseControlOfHost".equals(sa.getParam("Duration")) && host.getController() != sa.getActivatingPlayer()) {
|
||||
if ("UntilLoseControlOfHost".equals(duration) && host.getController() != sa.getActivatingPlayer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -92,7 +94,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
addLeaveBattlefieldReplacement(gameCard, sa, sa.getParam("LeaveBattlefield"));
|
||||
}
|
||||
|
||||
if (!"Permanent".equals(sa.getParam("Duration"))) {
|
||||
if (!"Permanent".equals(duration)) {
|
||||
// If not Permanent, remove Pumped at EOT
|
||||
final GameCommand untilEOT = new GameCommand() {
|
||||
private static final long serialVersionUID = -42244224L;
|
||||
@@ -123,9 +125,11 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
private static void applyPump(final SpellAbility sa, final Player p,
|
||||
final List<String> keywords, final long timestamp) {
|
||||
final Card host = sa.getHostCard();
|
||||
final String duration = sa.getParam("Duration");
|
||||
|
||||
//if host is not on the battlefield don't apply
|
||||
// Suspend should does Affect the Stack
|
||||
if (("UntilHostLeavesPlay".equals(sa.getParam("Duration")) || "UntilLoseControlOfHost".equals(sa.getParam("Duration")))
|
||||
if (((duration != null && duration.startsWith("UntilHostLeavesPlay")) || "UntilLoseControlOfHost".equals(duration))
|
||||
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
|
||||
return;
|
||||
}
|
||||
@@ -134,7 +138,7 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
p.addChangedKeywords(keywords, ImmutableList.of(), timestamp, 0);
|
||||
}
|
||||
|
||||
if (!"Permanent".equals(sa.getParam("Duration"))) {
|
||||
if (!"Permanent".equals(duration)) {
|
||||
// If not Permanent, remove Pumped at EOT
|
||||
final GameCommand untilEOT = new GameCommand() {
|
||||
private static final long serialVersionUID = -32453460L;
|
||||
@@ -185,6 +189,16 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
keywords.addAll(Arrays.asList(sa.getParam("KW").split(" & ")));
|
||||
}
|
||||
|
||||
if (sa.hasParam("IfDesc")) {
|
||||
if (sa.getParam("IfDesc").equals("True") && sa.hasParam("SpellDescription")) {
|
||||
String ifDesc = sa.getParam("SpellDescription");
|
||||
sb.append(ifDesc, 0, ifDesc.indexOf(",") + 1);
|
||||
} else {
|
||||
sb.append(sa.getParam("IfDesc"));
|
||||
}
|
||||
sb.append(" ");
|
||||
}
|
||||
|
||||
if (sa instanceof AbilitySub & sa.getRootAbility().getTargets().containsAll(tgts)) {
|
||||
//try to avoid having the same long list of targets twice in a StackDescription
|
||||
sb.append(tgts.size() == 1 && tgts.get(0) instanceof Card ? "It " : "They ");
|
||||
|
||||
@@ -109,7 +109,6 @@ public class RepeatEachEffect extends SpellAbilityEffect {
|
||||
} else {
|
||||
source.addRemembered(card);
|
||||
}
|
||||
|
||||
AbilityUtils.resolve(repeat);
|
||||
if (useImprinted) {
|
||||
source.removeImprintedCard(card);
|
||||
|
||||
@@ -4,8 +4,12 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import forge.game.Game;
|
||||
@@ -13,11 +17,15 @@ import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.TokenCreateTable;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementResult;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Localizer;
|
||||
|
||||
public class ReplaceTokenEffect extends SpellAbilityEffect {
|
||||
|
||||
@@ -26,14 +34,18 @@ public class ReplaceTokenEffect extends SpellAbilityEffect {
|
||||
final Card card = sa.getHostCard();
|
||||
final Player p = sa.getActivatingPlayer();
|
||||
final Game game = card.getGame();
|
||||
SpellAbility repSA = sa;
|
||||
|
||||
if (repSA.getReplacingObjects().isEmpty()) {
|
||||
repSA = sa.getRootAbility();
|
||||
}
|
||||
// ReplaceToken Effect only applies to one Player
|
||||
Player affected = (Player) sa.getReplacingObject(AbilityKey.Player);
|
||||
TokenCreateTable table = (TokenCreateTable) sa.getReplacingObject(AbilityKey.Token);
|
||||
Player affected = (Player) repSA.getReplacingObject(AbilityKey.Player);
|
||||
TokenCreateTable table = (TokenCreateTable) repSA.getReplacingObject(AbilityKey.Token);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa
|
||||
.getReplacingObject(AbilityKey.OriginalParams);
|
||||
Map<AbilityKey, Object> originalParams =
|
||||
(Map<AbilityKey, Object>) repSA.getReplacingObject(AbilityKey.OriginalParams);
|
||||
|
||||
// currently the only ones that changes the amount does double it
|
||||
if ("Amount".equals(sa.getParam("Type"))) {
|
||||
@@ -78,36 +90,56 @@ public class ReplaceTokenEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
} else if ("ReplaceToken".equals(sa.getParam("Type"))) {
|
||||
Card chosen = null;
|
||||
if (sa.hasParam("ValidChoices")) {
|
||||
CardCollectionView choices = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("ValidChoices").split(","), p, card, sa);
|
||||
if (choices.isEmpty()) {
|
||||
originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.NotReplaced);
|
||||
return;
|
||||
}
|
||||
chosen = p.getController().chooseSingleEntityForEffect(choices, sa, Localizer.getInstance().getMessage("lblChooseaCard"), false, null);
|
||||
}
|
||||
|
||||
long timestamp = game.getNextTimestamp();
|
||||
|
||||
Map<Player, Integer> toInsertMap = Maps.newHashMap();
|
||||
Multimap<Player, Pair<Integer, Iterable<Object>>> toInsertMap = ArrayListMultimap.create();
|
||||
Set<Card> toRemoveSet = Sets.newHashSet();
|
||||
for (Map.Entry<Card, Integer> e : table.row(affected).entrySet()) {
|
||||
if (!sa.matchesValidParam("ValidCard", e.getKey())) {
|
||||
continue;
|
||||
}
|
||||
Player controller = e.getKey().getController();
|
||||
int old = ObjectUtils.defaultIfNull(toInsertMap.get(controller), 0);
|
||||
toInsertMap.put(controller, old + e.getValue());
|
||||
// TODO should still merge the amounts to avoid additional prototypes when sourceSA doesn't use ForEach
|
||||
//int old = ObjectUtils.defaultIfNull(toInsertMap.get(controller), 0);
|
||||
Pair<Integer, Iterable<Object>> tokenAmountPair = new ImmutablePair<>(e.getValue(), e.getKey().getRemembered());
|
||||
toInsertMap.put(controller, tokenAmountPair);
|
||||
toRemoveSet.add(e.getKey());
|
||||
}
|
||||
// remove replaced tokens
|
||||
table.row(affected).keySet().removeAll(toRemoveSet);
|
||||
|
||||
// insert new tokens
|
||||
for (Map.Entry<Player, Integer> pe : toInsertMap.entrySet()) {
|
||||
if (pe.getValue() <= 0) {
|
||||
for (Map.Entry<Player, Pair<Integer, Iterable<Object>>> pe : toInsertMap.entries()) {
|
||||
int amt = pe.getValue().getLeft();
|
||||
if (amt <= 0) {
|
||||
continue;
|
||||
}
|
||||
for (String script : sa.getParam("TokenScript").split(",")) {
|
||||
final Card token = TokenInfo.getProtoType(script, sa, pe.getKey());
|
||||
final Card token;
|
||||
if (script.equals("Chosen")) {
|
||||
token = CopyPermanentEffect.getProtoType(sa, chosen, pe.getKey());
|
||||
} else {
|
||||
token = TokenInfo.getProtoType(script, sa, pe.getKey());
|
||||
}
|
||||
|
||||
if (token == null) {
|
||||
throw new RuntimeException("don't find Token for TokenScript: " + script);
|
||||
}
|
||||
|
||||
token.setController(pe.getKey(), timestamp);
|
||||
table.put(affected, token, pe.getValue());
|
||||
// if token is created from ForEach keep that
|
||||
token.addRemembered(pe.getValue().getRight());
|
||||
table.put(affected, token, amt);
|
||||
}
|
||||
}
|
||||
} else if ("ReplaceController".equals(sa.getParam("Type"))) {
|
||||
|
||||
@@ -54,8 +54,6 @@ public class RestartGameEffect extends SpellAbilityEffect {
|
||||
|
||||
game.getStack().reset();
|
||||
game.clearCounterAddedThisTurn();
|
||||
game.resetPlayersAttackedOnNextTurn();
|
||||
game.resetPlayersAttackedOnNextTurn();
|
||||
game.setMonarch(null);
|
||||
game.setDayTime(null);
|
||||
GameAction action = game.getAction();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
@@ -18,25 +19,27 @@ import forge.game.card.CardZoneTable;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Lang;
|
||||
|
||||
public class SacrificeAllEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
// when getStackDesc is called, just build exactly what is happening
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
/*
|
||||
* This is not currently targeted ArrayList<Player> tgtPlayers;
|
||||
*
|
||||
* Target tgt = af.getAbTgt(); if (tgt != null) tgtPlayers =
|
||||
* tgt.getTargetPlayers(); else tgtPlayers =
|
||||
* AbilityFactory.getDefinedPlayers(sa.getHostCard(),
|
||||
* sa.get("Defined"), sa);
|
||||
*/
|
||||
|
||||
sb.append("Sacrifice permanents.");
|
||||
if (sa.hasParam("Controller")) {
|
||||
List<Player> conts = getDefinedPlayersOrTargeted(sa, "Controller");
|
||||
sb.append(Lang.joinHomogenous(conts)).append(conts.size() == 1 ? " sacrifices " : " sacrifice ");
|
||||
} else {
|
||||
sb.append("Sacrifice ");
|
||||
}
|
||||
if (sa.hasParam("Defined")) {
|
||||
List<Card> toSac = getDefinedCardsOrTargeted(sa);
|
||||
sb.append(Lang.joinHomogenous(toSac)).append(".");
|
||||
} else {
|
||||
sb.append("permanents.");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -38,11 +38,21 @@ public class TokenEffect extends TokenEffectBase {
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
if (sa.hasParam("SpellDescription")) {
|
||||
final Card host = sa.getHostCard();
|
||||
String desc = sa.getParam("SpellDescription");
|
||||
if (StringUtils.containsIgnoreCase(desc,"Create")) {
|
||||
final Card host = sa.getHostCard();
|
||||
final List<Player> creators = AbilityUtils.getDefinedPlayers(host, sa.getParamOrDefault("TokenOwner",
|
||||
"You"), sa);
|
||||
List<String> words = Arrays.asList(desc.split(" "));
|
||||
final List<Player> creators = AbilityUtils.getDefinedPlayers(host, sa.getParamOrDefault("TokenOwner",
|
||||
"You"), sa);
|
||||
if (!words.get(0).equalsIgnoreCase("Create") && desc.contains(" create")) {
|
||||
String[] parts = desc.split(" create", 2);
|
||||
desc = parts[0] + " " + Lang.joinHomogenous(creators) + " create" + parts[1];
|
||||
if (creators.size() == 1) {
|
||||
desc = desc.replaceAll(" create ", " creates ");
|
||||
}
|
||||
if (desc.contains("you")) {
|
||||
desc = desc.replaceAll("you", sa.getActivatingPlayer().getName());
|
||||
}
|
||||
} else if (StringUtils.containsIgnoreCase(desc,"Create")) {
|
||||
String verb = creators.size() == 1 ? "creates" : "create";
|
||||
String start = Lang.joinHomogenous(creators) + " " + verb;
|
||||
String create = desc.contains("Create") ? "Create" : "create";
|
||||
@@ -54,7 +64,6 @@ public class TokenEffect extends TokenEffectBase {
|
||||
if (numTokens != 0) { //0 probably means calculation isn't ready in time for stack
|
||||
if (numTokens != 1) { //if we are making more than one, substitute the numeral for a/an
|
||||
String numeral = " " + Lang.getNumeral(numTokens) + " ";
|
||||
List<String> words = Arrays.asList(desc.split(" "));
|
||||
String target = " " + words.get(words.indexOf(verb) + 1) + " ";
|
||||
desc = desc.replaceFirst(target, numeral);
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
|
||||
Player creator = c.getRowKey();
|
||||
Player controller = prototype.getController();
|
||||
int cellAmount = c.getValue();
|
||||
|
||||
for (int i = 0; i < cellAmount; i++) {
|
||||
Card tok = CardFactory.copyCard(prototype, true);
|
||||
// Crafty Cutpurse would change under which control it does enter,
|
||||
@@ -170,6 +171,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
|
||||
if (clone) {
|
||||
moved.setCloneOrigin(host);
|
||||
}
|
||||
|
||||
if (!pumpKeywords.isEmpty()) {
|
||||
moved.addChangedCardKeywords(pumpKeywords, Lists.newArrayList(), false, timestamp, 0);
|
||||
addPumpUntil(sa, moved, timestamp);
|
||||
@@ -202,6 +204,10 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
|
||||
moved.addRemembered(AbilityUtils.getDefinedObjects(host, sa.getParam("TokenRemembered"), sa));
|
||||
}
|
||||
allTokens.add(moved);
|
||||
|
||||
if (sa.hasParam("CleanupForEach")) {
|
||||
moved.removeRemembered(prototype.getRemembered());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +238,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
|
||||
|
||||
boolean canAttach = lki.isAttachment();
|
||||
|
||||
if (canAttach && !ge.canBeAttached(lki)) {
|
||||
if (canAttach && !ge.canBeAttached(lki, sa)) {
|
||||
canAttach = false;
|
||||
}
|
||||
|
||||
@@ -248,7 +254,7 @@ public abstract class TokenEffectBase extends SpellAbilityEffect {
|
||||
return false;
|
||||
}
|
||||
|
||||
tok.attachToEntity(ge);
|
||||
tok.attachToEntity(ge, sa);
|
||||
return true;
|
||||
}
|
||||
// not a GameEntity, cant be attach
|
||||
|
||||
@@ -81,14 +81,14 @@ public class ZoneExchangeEffect extends SpellAbilityEffect {
|
||||
Card c = null;
|
||||
if (type != null && type.equals("Aura") && object1.getEnchantingCard() != null) {
|
||||
c = object1.getEnchantingCard();
|
||||
if (!c.canBeAttached(object2)) {
|
||||
if (!c.canBeAttached(object2, sa)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Enchant first
|
||||
if (c != null) {
|
||||
object1.unattachFromEntity(c);
|
||||
object2.attachToEntity(c);
|
||||
object2.attachToEntity(c, sa);
|
||||
}
|
||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
moveParams.put(AbilityKey.LastStateBattlefield, sa.getLastStateBattlefield());
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
package forge.game.card;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.ForwardingTable;
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
import forge.game.StaticLayerInterface;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
|
||||
public class ActivationTable extends ForwardingTable<SpellAbility, List<StaticLayerInterface>, Integer> {
|
||||
Table<SpellAbility, List<StaticLayerInterface>, Integer> dataTable = HashBasedTable.create();
|
||||
public class ActivationTable extends ForwardingTable<SpellAbility, Optional<StaticAbility>, Integer> {
|
||||
Table<SpellAbility, Optional<StaticAbility>, Integer> dataTable = HashBasedTable.create();
|
||||
|
||||
@Override
|
||||
protected Table<SpellAbility, List<StaticLayerInterface>, Integer> delegate() {
|
||||
protected Table<SpellAbility, Optional<StaticAbility>, Integer> delegate() {
|
||||
return dataTable;
|
||||
}
|
||||
|
||||
@@ -37,7 +36,7 @@ public class ActivationTable extends ForwardingTable<SpellAbility, List<StaticLa
|
||||
SpellAbility original = getOriginal(sa);
|
||||
|
||||
if (original != null) {
|
||||
List<StaticLayerInterface> st = root.getGrantedByStatic();
|
||||
Optional<StaticAbility> st = Optional.fromNullable(root.getGrantorStatic());
|
||||
|
||||
delegate().put(original, st, ObjectUtils.defaultIfNull(get(original, st), 0) + 1);
|
||||
}
|
||||
@@ -46,7 +45,7 @@ public class ActivationTable extends ForwardingTable<SpellAbility, List<StaticLa
|
||||
public Integer get(SpellAbility sa) {
|
||||
SpellAbility root = sa.getRootAbility();
|
||||
SpellAbility original = getOriginal(sa);
|
||||
List<StaticLayerInterface> st = root.getGrantedByStatic();
|
||||
Optional<StaticAbility> st = Optional.fromNullable(root.getGrantorStatic());
|
||||
|
||||
if (contains(original, st)) {
|
||||
return get(original, st);
|
||||
|
||||
@@ -36,7 +36,6 @@ import forge.game.GameEntityCounterTable;
|
||||
import forge.game.GameStage;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.IHasSVars;
|
||||
import forge.game.StaticLayerInterface;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -61,6 +60,7 @@ import forge.game.staticability.StaticAbilityCantPutCounter;
|
||||
import forge.game.staticability.StaticAbilityCantSacrifice;
|
||||
import forge.game.staticability.StaticAbilityCantTarget;
|
||||
import forge.game.staticability.StaticAbilityCantTransform;
|
||||
import forge.game.staticability.StaticAbilityIgnoreLegendRule;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.trigger.TriggerType;
|
||||
@@ -75,7 +75,6 @@ import forge.util.collect.FCollection;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import io.sentry.Breadcrumb;
|
||||
import io.sentry.Sentry;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.mutable.MutableBoolean;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
@@ -111,7 +110,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
private final Table<Long, Long, List<String>> hiddenExtrinsicKeywords = TreeBasedTable.create();
|
||||
|
||||
// cards attached or otherwise linked to this card
|
||||
private CardCollection hauntedBy, devouredCards, exploitedCards, delvedCards, convokedCards, imprintedCards, encodedCards;
|
||||
private CardCollection hauntedBy, devouredCards, exploitedCards, delvedCards, convokedCards, imprintedCards,
|
||||
exiledCards, encodedCards;
|
||||
private CardCollection gainControlTargets, chosenCards;
|
||||
private CardCollection mergedCards;
|
||||
private Map<Long, CardCollection> mustBlockCards = Maps.newHashMap();
|
||||
@@ -151,13 +151,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
private final Table<Long, Long, CardTraitChanges> changedCardTraits = TreeBasedTable.create(); // Layer 6
|
||||
|
||||
// stores the card traits created by static abilities
|
||||
private final Table<StaticAbility, String, SpellAbility> storedSpellAbility = TreeBasedTable.create();
|
||||
private final Table<StaticAbility, String, SpellAbility> storedSpellAbilility = TreeBasedTable.create();
|
||||
private final Table<StaticAbility, String, Trigger> storedTrigger = TreeBasedTable.create();
|
||||
private final Table<StaticAbility, String, ReplacementEffect> storedReplacementEffect = TreeBasedTable.create();
|
||||
private final Table<StaticAbility, String, StaticAbility> storedStaticAbility = TreeBasedTable.create();
|
||||
|
||||
private final Table<StaticLayerInterface, SpellAbility, SpellAbility> copiedSpellAbility = HashBasedTable.create();
|
||||
|
||||
// x=timestamp y=StaticAbility id
|
||||
private final Table<Long, Long, CardColor> changedCardColorsByText = TreeBasedTable.create(); // Layer 3 by Text Change
|
||||
private final Table<Long, Long, CardColor> changedCardColorsCharacterDefining = TreeBasedTable.create(); // Layer 5 CDA
|
||||
@@ -199,6 +197,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|
||||
private boolean startsGameInPlay = false;
|
||||
private boolean drawnThisTurn = false;
|
||||
private boolean foughtThisTurn = false;
|
||||
private boolean becameTargetThisTurn = false;
|
||||
private boolean startedTheTurnUntapped = false;
|
||||
private boolean cameUnderControlSinceLastUpkeep = true; // for Echo
|
||||
@@ -333,11 +332,17 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
private final ActivationTable numberGameActivations = new ActivationTable();
|
||||
private final ActivationTable numberAbilityResolved = new ActivationTable();
|
||||
|
||||
private final LinkedAbilityTable<String> chosenModesTurn = new LinkedAbilityTable<String>();
|
||||
private final LinkedAbilityTable<String> chosenModesGame = new LinkedAbilityTable<String>();
|
||||
private final Map<SpellAbility, List<String>> chosenModesTurn = Maps.newHashMap();
|
||||
private final Map<SpellAbility, List<String>> chosenModesGame = Maps.newHashMap();
|
||||
|
||||
private final Table<SpellAbility, StaticAbility, List<String>> chosenModesTurnStatic = HashBasedTable.create();
|
||||
private final Table<SpellAbility, StaticAbility, List<String>> chosenModesGameStatic = HashBasedTable.create();
|
||||
|
||||
private CombatLki combatLKI;
|
||||
|
||||
private ReplacementEffect shieldCounterReplaceDamage = null;
|
||||
private ReplacementEffect shieldCounterReplaceDestroy = null;
|
||||
|
||||
// Enumeration for CMC request types
|
||||
public enum SplitCMCMode {
|
||||
CurrentSideCMC,
|
||||
@@ -530,7 +535,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
return currentStateName;
|
||||
}
|
||||
|
||||
// use by CopyPermament
|
||||
// use by CopyPermanent
|
||||
public void setStates(Map<CardStateName, CardState> map) {
|
||||
states.clear();
|
||||
states.putAll(map);
|
||||
@@ -1080,6 +1085,31 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
imprintedCards = view.clearCards(imprintedCards, TrackableProperty.ImprintedCards);
|
||||
}
|
||||
|
||||
public final CardCollectionView getExiledCards() {
|
||||
return CardCollection.getView(exiledCards);
|
||||
}
|
||||
public final boolean hasExiledCard() {
|
||||
return FCollection.hasElements(exiledCards);
|
||||
}
|
||||
public final boolean hasExiledCard(Card c) {
|
||||
return FCollection.hasElement(exiledCards, c);
|
||||
}
|
||||
public final void addExiledCard(final Card c) {
|
||||
exiledCards = view.addCard(exiledCards, c, TrackableProperty.ExiledCards);
|
||||
}
|
||||
public final void addExiledCards(final Iterable<Card> cards) {
|
||||
exiledCards = view.addCards(exiledCards, cards, TrackableProperty.ExiledCards);
|
||||
}
|
||||
public final void removeExiledCard(final Card c) {
|
||||
exiledCards = view.removeCard(exiledCards, c, TrackableProperty.ExiledCards);
|
||||
}
|
||||
public final void removeExiledCards(final Iterable<Card> cards) {
|
||||
exiledCards = view.removeCards(exiledCards, cards, TrackableProperty.ExiledCards);
|
||||
}
|
||||
public final void clearExiledCards() {
|
||||
exiledCards = view.clearCards(exiledCards, TrackableProperty.ExiledCards);
|
||||
}
|
||||
|
||||
public final CardCollectionView getEncodedCards() {
|
||||
return CardCollection.getView(encodedCards);
|
||||
}
|
||||
@@ -1669,6 +1699,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
return;
|
||||
}
|
||||
|
||||
exiledWith.removeExiledCard(this);
|
||||
exiledWith.removeUntilLeavesBattlefield(this);
|
||||
|
||||
exiledWith = null;
|
||||
@@ -1834,6 +1865,13 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
drawnThisTurn = b;
|
||||
}
|
||||
|
||||
public final boolean getFoughtThisTurn() {
|
||||
return foughtThisTurn;
|
||||
}
|
||||
public final void setFoughtThisTurn(final boolean b) {
|
||||
foughtThisTurn = b;
|
||||
}
|
||||
|
||||
public final CardCollectionView getGainControlTargets() { //used primarily with AbilityFactory_GainControl
|
||||
return CardCollection.getView(gainControlTargets);
|
||||
}
|
||||
@@ -1982,15 +2020,17 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
} else if (keyword.startsWith("Alternative Cost")) {
|
||||
sbLong.append("Has alternative cost.");
|
||||
} else if (keyword.startsWith("AlternateAdditionalCost")) {
|
||||
final String costString1 = keyword.split(":")[1];
|
||||
final String costString2 = keyword.split(":")[2];
|
||||
final Cost cost1 = new Cost(costString1, false);
|
||||
final Cost cost2 = new Cost(costString2, false);
|
||||
sbLong.append("As an additional cost to cast this spell, ")
|
||||
.append(StringUtils.uncapitalize(cost1.toSimpleString()))
|
||||
.append(" or pay ")
|
||||
.append(StringUtils.uncapitalize(cost2.toSimpleString()))
|
||||
.append(".\r\n\r\n");
|
||||
final String[] costs = keyword.split(":", 2)[1].split(":");
|
||||
sbLong.append("As an additional cost to cast this spell, ");
|
||||
for (int n = 0; n < costs.length; n++) {
|
||||
final Cost cost = new Cost(costs[n], false);
|
||||
if (cost.isOnlyManaCost()) {
|
||||
sbLong.append(" pay ");
|
||||
}
|
||||
sbLong.append(StringUtils.uncapitalize(cost.toSimpleString()));
|
||||
sbLong.append(n + 1 == costs.length ? ".\r\n\r\n" : n + 2 == costs.length && costs.length > 2
|
||||
? ", or " : n + 2 == costs.length ? " or " : ", ");
|
||||
}
|
||||
} else if (keyword.startsWith("Multikicker")) {
|
||||
if (!keyword.endsWith("Generic")) {
|
||||
final String[] n = keyword.split(":");
|
||||
@@ -2051,7 +2091,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|| keyword.equals("Ascend") || keyword.equals("Totem armor")
|
||||
|| keyword.equals("Battle cry") || keyword.equals("Devoid") || keyword.equals("Riot")
|
||||
|| keyword.equals("Daybound") || keyword.equals("Nightbound")
|
||||
|| keyword.equals("Friends forever")) {
|
||||
|| keyword.equals("Friends forever") || keyword.equals("Choose a Background")) {
|
||||
sbLong.append(keyword).append(" (").append(inst.getReminderText()).append(")");
|
||||
} else if (keyword.startsWith("Partner:")) {
|
||||
final String[] k = keyword.split(":");
|
||||
@@ -2543,7 +2583,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|| keyword.equals("Devoid") || keyword.equals("Lifelink")
|
||||
|| keyword.equals("Split second")) {
|
||||
sbBefore.append(keyword).append(" (").append(inst.getReminderText()).append(")");
|
||||
sbBefore.append("\r\n");
|
||||
sbBefore.append("\r\n\r\n");
|
||||
} else if (keyword.equals("Conspire") || keyword.equals("Epic")
|
||||
|| keyword.equals("Suspend") || keyword.equals("Jump-start")
|
||||
|| keyword.equals("Fuse")) {
|
||||
@@ -2594,7 +2634,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
final Cost cost2 = new Cost(n[2], false);
|
||||
sbx.append(cost2.toSimpleString());
|
||||
}
|
||||
sbx.append(" (").append(inst.getReminderText()).append(")");
|
||||
sbx.append(" (").append(inst.getReminderText()).append(")\r\n");
|
||||
} else {
|
||||
sbx.append("As an additional cost to cast this spell, you may ");
|
||||
String costS = StringUtils.uncapitalize(cost.toSimpleString());
|
||||
@@ -2603,14 +2643,17 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
sbBefore.append(sbx).append("\r\n");
|
||||
} else if (keyword.startsWith("AlternateAdditionalCost")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final Cost cost1 = new Cost(k[1], false);
|
||||
final Cost cost2 = new Cost(k[2], false);
|
||||
sbBefore.append("As an additional cost to cast this spell, ")
|
||||
.append(StringUtils.uncapitalize(cost1.toSimpleString()))
|
||||
.append(" or pay ")
|
||||
.append(StringUtils.uncapitalize(cost2.toSimpleString()))
|
||||
.append(".\r\n\r\n");
|
||||
final String[] costs = keyword.split(":", 2)[1].split(":");
|
||||
sbBefore.append("As an additional cost to cast this spell, ");
|
||||
for (int n = 0; n < costs.length; n++) {
|
||||
final Cost cost = new Cost(costs[n], false);
|
||||
if (cost.isOnlyManaCost()) {
|
||||
sbBefore.append(" pay ");
|
||||
}
|
||||
sbBefore.append(StringUtils.uncapitalize(cost.toSimpleString()));
|
||||
sbBefore.append(n + 1 == costs.length ? ".\r\n\r\n" : n + 2 == costs.length && costs.length > 2
|
||||
? ", or " : n + 2 == costs.length ? " or " : ", ");
|
||||
}
|
||||
} else if (keyword.startsWith("Presence") || keyword.startsWith("MayFlash")) {
|
||||
// Pseudo keywords, only print Reminder
|
||||
sbBefore.append(inst.getReminderText());
|
||||
@@ -3263,8 +3306,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
public final void removeTempController(final Player player) {
|
||||
boolean changed = false;
|
||||
// Remove each key that yields this player
|
||||
this.tempControllers.values().remove(player);
|
||||
while (tempControllers.values().remove(player)) {
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
view.updateController(this);
|
||||
}
|
||||
}
|
||||
|
||||
public final void clearTempControllers() {
|
||||
@@ -3406,22 +3455,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
return this.isAttachedToEntity();
|
||||
}
|
||||
|
||||
public final void equipCard(final Card c) {
|
||||
if (!isEquipment()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.attachToEntity(c);
|
||||
}
|
||||
|
||||
public final void fortifyCard(final Card c) {
|
||||
if (!isFortification()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.attachToEntity(c);
|
||||
}
|
||||
|
||||
public final void unEquipCard(final Card c) { // equipment.unEquipCard(equippedCard);
|
||||
this.unattachFromEntity(c);
|
||||
}
|
||||
@@ -3477,11 +3510,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
return getEnchantingCard() != null;
|
||||
}
|
||||
|
||||
public final void attachToEntity(final GameEntity entity) {
|
||||
attachToEntity(entity, false);
|
||||
public final void attachToEntity(final GameEntity entity, SpellAbility sa) {
|
||||
attachToEntity(entity, sa, false);
|
||||
}
|
||||
public final void attachToEntity(final GameEntity entity, boolean overwrite) {
|
||||
if (!overwrite && !entity.canBeAttached(this)) {
|
||||
public final void attachToEntity(final GameEntity entity, SpellAbility sa, boolean overwrite) {
|
||||
if (!overwrite && !entity.canBeAttached(this, sa)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4255,12 +4288,12 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
public final SpellAbility getSpellAbilityForStaticAbility(final String str, final StaticAbility stAb) {
|
||||
SpellAbility result = storedSpellAbility.get(stAb, str);
|
||||
SpellAbility result = storedSpellAbilility.get(stAb, str);
|
||||
if (result == null) {
|
||||
result = AbilityFactory.getAbility(str, this, stAb);
|
||||
result.setIntrinsic(false);
|
||||
result.addGrantedByStatic(stAb);
|
||||
storedSpellAbility.put(stAb, str, result);
|
||||
result.setGrantorStatic(stAb);
|
||||
storedSpellAbilility.put(stAb, str, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -4269,7 +4302,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
Trigger result = storedTrigger.get(stAb, str);
|
||||
if (result == null) {
|
||||
result = TriggerHandler.parseTrigger(str, this, false, stAb);
|
||||
result.addGrantedByStatic(stAb);
|
||||
storedTrigger.put(stAb, str, result);
|
||||
}
|
||||
return result;
|
||||
@@ -4280,7 +4312,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
ReplacementEffect result = storedReplacementEffect.get(stAb, str);
|
||||
if (result == null) {
|
||||
result = ReplacementHandler.parseReplacement(str, this, false, stAb);
|
||||
result.addGrantedByStatic(stAb);
|
||||
storedReplacementEffect.put(stAb, str, result);
|
||||
}
|
||||
return result;
|
||||
@@ -4290,28 +4321,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
StaticAbility result = storedStaticAbility.get(stAb, str);
|
||||
if (result == null) {
|
||||
result = StaticAbility.create(str, this, stAb.getCardState(), false);
|
||||
result.addGrantedByStatic(stAb);
|
||||
storedStaticAbility.put(stAb, str, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public final SpellAbility copySpellAbilityForStaticAbility(final SpellAbility sa, final StaticAbility stAb, final boolean intrinsic) {
|
||||
SpellAbility result = copiedSpellAbility.get(stAb, sa);
|
||||
if (result == null) {
|
||||
result = sa.copy(this, false);
|
||||
if (stAb.hasParam("GainsAbilitiesLimitPerTurn")) {
|
||||
result.setRestrictions(sa.getRestrictions());
|
||||
result.getRestrictions().setLimitToCheck(stAb.getParam("GainsAbilitiesLimitPerTurn"));
|
||||
}
|
||||
result.setOriginalAbility(sa.getOriginalAbility()); // need to be set to get the Once Per turn Clause correct
|
||||
result.addGrantedByStatic(stAb);
|
||||
result.setIntrinsic(intrinsic);
|
||||
copiedSpellAbility.put(stAb, sa, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public final void addChangedCardTraits(Collection<SpellAbility> spells, Collection<SpellAbility> removedAbilities,
|
||||
Collection<Trigger> trigger, Collection<ReplacementEffect> replacements, Collection<StaticAbility> statics,
|
||||
boolean removeAll, boolean removeNonMana, long timestamp, long staticId) {
|
||||
@@ -5179,18 +5193,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
} else if (getName().isEmpty()) {
|
||||
// if this does not have a name, then there is no name to share
|
||||
return false;
|
||||
} else {
|
||||
} else if (StaticData.instance().getCommonCards().isNonLegendaryCreatureName(getName())) {
|
||||
// check if this card has a name from a face
|
||||
// in general token creatures does not have this
|
||||
final ICardFace face = StaticData.instance().getCommonCards().getFaceByName(getName());
|
||||
if (face == null) {
|
||||
return false;
|
||||
}
|
||||
// TODO add check if face is legal in the format of the game
|
||||
// name does need to be a non-legendary creature
|
||||
final CardType type = face.getType();
|
||||
if (type != null && type.isCreature() && !type.isLegendary())
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return sharesNameWith(c1.getName());
|
||||
@@ -5213,15 +5219,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
if (!shares && hasNonLegendaryCreatureNames()) {
|
||||
// check if the name is from a face
|
||||
// in general token creatures does not have this
|
||||
final ICardFace face = StaticData.instance().getCommonCards().getFaceByName(name);
|
||||
if (face == null) {
|
||||
return false;
|
||||
}
|
||||
// TODO add check if face is legal in the format of the game
|
||||
// name does need to be a non-legendary creature
|
||||
final CardType type = face.getType();
|
||||
if (type.isCreature() && !type.isLegendary())
|
||||
return true;
|
||||
return StaticData.instance().getCommonCards().isNonLegendaryCreatureName(name);
|
||||
}
|
||||
return shares;
|
||||
}
|
||||
@@ -6067,17 +6065,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final boolean canBeEquippedBy(final Card equip) {
|
||||
if (isCreature() && isInPlay()) {
|
||||
return true;
|
||||
} else if (isPlaneswalker() && isInPlay()) {
|
||||
for (KeywordInterface inst : equip.getKeywords(Keyword.EQUIP)) {
|
||||
if (inst.getOriginal().contains("planeswalker")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
protected final boolean canBeEquippedBy(final Card equip, SpellAbility sa) {
|
||||
if (!isInPlay()) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
if (sa != null && sa.isEquip()) {
|
||||
return isValid(sa.getTargetRestrictions().getValidTgts(), sa.getActivatingPlayer(), equip, sa);
|
||||
}
|
||||
return isCreature();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -6089,13 +6084,13 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
* @see forge.game.GameEntity#canBeAttached(forge.game.card.Card, boolean)
|
||||
*/
|
||||
@Override
|
||||
public boolean canBeAttached(Card attach, boolean checkSBA) {
|
||||
public boolean canBeAttached(Card attach, SpellAbility sa, boolean checkSBA) {
|
||||
// phase check there
|
||||
if (isPhasedOut() && !attach.isPhasedOut()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.canBeAttached(attach, checkSBA);
|
||||
return super.canBeAttached(attach, sa, checkSBA);
|
||||
}
|
||||
|
||||
public FCollectionView<ReplacementEffect> getReplacementEffects() {
|
||||
@@ -6124,6 +6119,26 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
for (KeywordInterface kw : getUnhiddenKeywords(state)) {
|
||||
list.addAll(kw.getReplacements());
|
||||
}
|
||||
|
||||
// Shield Counter aren't affected by Changed Card Traits
|
||||
if (this.getCounters(CounterEnumType.SHIELD) > 0) {
|
||||
String sa = "DB$ RemoveCounter | Defined$ Self | CounterType$ Shield | CounterNum$ 1";
|
||||
if (shieldCounterReplaceDamage == null) {
|
||||
String reStr = "Event$ DamageDone | ActiveZones$ Battlefield | ValidTarget$ Card.Self | PreventionEffect$ True | AlwaysReplace$ True "
|
||||
+ "| Description$ If damage would be dealt to this permanent, prevent that damage and remove a shield counter from it.";
|
||||
shieldCounterReplaceDamage = ReplacementHandler.parseReplacement(reStr, this, false, null);
|
||||
shieldCounterReplaceDamage.setOverridingAbility(AbilityFactory.getAbility(sa, this));
|
||||
}
|
||||
if (shieldCounterReplaceDestroy == null) {
|
||||
String reStr = "Event$ Destroy | ActiveZones$ Battlefield | ValidCard$ Card.Self | ValidSource$ SpellAbility "
|
||||
+ "| Description$ If this permanent would be destroyed as the result of an effect, instead remove a shield counter from it.";
|
||||
shieldCounterReplaceDestroy = ReplacementHandler.parseReplacement(reStr, this, false, null);
|
||||
shieldCounterReplaceDestroy.setOverridingAbility(AbilityFactory.getAbility(sa, this));
|
||||
}
|
||||
|
||||
list.add(shieldCounterReplaceDamage);
|
||||
list.add(shieldCounterReplaceDestroy);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasReplacementEffect(final ReplacementEffect re) {
|
||||
@@ -6207,12 +6222,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
setRegeneratedThisTurn(0);
|
||||
resetShield();
|
||||
setBecameTargetThisTurn(false);
|
||||
setFoughtThisTurn(false);
|
||||
clearMustBlockCards();
|
||||
getDamageHistory().setCreatureAttackedLastTurnOf(turn, getDamageHistory().getCreatureAttacksThisTurn() > 0);
|
||||
getDamageHistory().newTurn();
|
||||
getDamageHistory().setCreatureAttackedLastTurnOf(turn, getDamageHistory().getCreatureAttackedThisTurn());
|
||||
getDamageHistory().setCreatureAttackedThisTurn(false);
|
||||
getDamageHistory().setCreatureAttacksThisTurn(0);
|
||||
getDamageHistory().setHasBeenDealtNonCombatDamageThisTurn(false);
|
||||
clearBlockedByThisTurn();
|
||||
clearBlockedThisTurn();
|
||||
resetMayPlayTurn();
|
||||
@@ -6883,19 +6896,91 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
public List<String> getChosenModesTurn(SpellAbility ability) {
|
||||
return chosenModesTurn.get(ability);
|
||||
SpellAbility original = null;
|
||||
SpellAbility root = ability.getRootAbility();
|
||||
|
||||
// because trigger spell abilities are copied, try to get original one
|
||||
if (root.isTrigger()) {
|
||||
original = root.getTrigger().getOverridingAbility();
|
||||
} else {
|
||||
original = ability.getOriginalAbility();
|
||||
if (original == null) {
|
||||
original = ability;
|
||||
}
|
||||
}
|
||||
|
||||
if (ability.getGrantorStatic() != null) {
|
||||
return chosenModesTurnStatic.get(original, ability.getGrantorStatic());
|
||||
}
|
||||
return chosenModesTurn.get(original);
|
||||
}
|
||||
public List<String> getChosenModesGame(SpellAbility ability) {
|
||||
return chosenModesGame.get(ability);
|
||||
SpellAbility original = null;
|
||||
SpellAbility root = ability.getRootAbility();
|
||||
|
||||
// because trigger spell abilities are copied, try to get original one
|
||||
if (root.isTrigger()) {
|
||||
original = root.getTrigger().getOverridingAbility();
|
||||
} else {
|
||||
original = ability.getOriginalAbility();
|
||||
if (original == null) {
|
||||
original = ability;
|
||||
}
|
||||
}
|
||||
|
||||
if (ability.getGrantorStatic() != null) {
|
||||
return chosenModesGameStatic.get(original, ability.getGrantorStatic());
|
||||
}
|
||||
return chosenModesGame.get(original);
|
||||
}
|
||||
|
||||
public void addChosenModes(SpellAbility ability, String mode) {
|
||||
this.chosenModesTurn.put(mode, ability);
|
||||
this.chosenModesGame.put(mode, ability);
|
||||
SpellAbility original = null;
|
||||
SpellAbility root = ability.getRootAbility();
|
||||
|
||||
// because trigger spell abilities are copied, try to get original one
|
||||
if (root.isTrigger()) {
|
||||
original = root.getTrigger().getOverridingAbility();
|
||||
} else {
|
||||
original = ability.getOriginalAbility();
|
||||
if (original == null) {
|
||||
original = ability;
|
||||
}
|
||||
}
|
||||
|
||||
if (ability.getGrantorStatic() != null) {
|
||||
List<String> result = chosenModesTurnStatic.get(original, ability.getGrantorStatic());
|
||||
if (result == null) {
|
||||
result = Lists.newArrayList();
|
||||
chosenModesTurnStatic.put(original, ability.getGrantorStatic(), result);
|
||||
}
|
||||
result.add(mode);
|
||||
result = chosenModesGameStatic.get(original, ability.getGrantorStatic());
|
||||
if (result == null) {
|
||||
result = Lists.newArrayList();
|
||||
chosenModesGameStatic.put(original, ability.getGrantorStatic(), result);
|
||||
}
|
||||
result.add(mode);
|
||||
} else {
|
||||
List<String> result = chosenModesTurn.get(original);
|
||||
if (result == null) {
|
||||
result = Lists.newArrayList();
|
||||
chosenModesTurn.put(original, result);
|
||||
}
|
||||
result.add(mode);
|
||||
|
||||
result = chosenModesGame.get(original);
|
||||
if (result == null) {
|
||||
result = Lists.newArrayList();
|
||||
chosenModesGame.put(original, result);
|
||||
}
|
||||
result.add(mode);
|
||||
}
|
||||
}
|
||||
|
||||
public void resetChosenModeTurn() {
|
||||
chosenModesTurn.clear();
|
||||
chosenModesTurnStatic.clear();
|
||||
}
|
||||
|
||||
public int getPlaneswalkerAbilityActivated() {
|
||||
@@ -7010,4 +7095,16 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
return getGame().getCombat().isAttacking(this);
|
||||
}
|
||||
|
||||
public boolean ignoreLegendRule() {
|
||||
// not legendary
|
||||
if (!getType().isLegendary()) {
|
||||
return true;
|
||||
}
|
||||
// empty name and no "has non legendary creature names"
|
||||
if (this.getName().isEmpty() && !hasNonLegendaryCreatureNames()) {
|
||||
return true;
|
||||
}
|
||||
return StaticAbilityIgnoreLegendRule.ignoreLegendRule(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,11 @@ import forge.game.player.Player;
|
||||
*/
|
||||
public class CardDamageHistory {
|
||||
|
||||
private boolean creatureAttackedThisTurn = false;
|
||||
private boolean creatureAttackedThisCombat = false;
|
||||
private boolean creatureBlockedThisCombat = false;
|
||||
private boolean creatureGotBlockedThisCombat = false;
|
||||
private boolean receivedNonCombatDamageThisTurn = false;
|
||||
private int attacksThisTurn = 0;
|
||||
private List<GameEntity> attackedThisTurn = Lists.newArrayList();
|
||||
|
||||
private final List<Player> creatureAttackedLastTurnOf = Lists.newArrayList();
|
||||
private final List<Player> NotAttackedSinceLastUpkeepOf = Lists.newArrayList();
|
||||
@@ -32,7 +31,7 @@ public class CardDamageHistory {
|
||||
private final Map<GameEntity, Integer> damagedThisTurn = Maps.newHashMap();
|
||||
private final Map<GameEntity, Integer> damagedThisTurnInCombat = Maps.newHashMap();
|
||||
private final Map<GameEntity, Integer> damagedThisGame = Maps.newHashMap();
|
||||
|
||||
|
||||
public final boolean getHasdealtDamagetoAny() {
|
||||
return !damagedThisGame.isEmpty();
|
||||
}
|
||||
@@ -47,12 +46,11 @@ public class CardDamageHistory {
|
||||
* @param hasAttacked
|
||||
* a boolean.
|
||||
*/
|
||||
public final void setCreatureAttackedThisCombat(final boolean hasAttacked) {
|
||||
this.creatureAttackedThisCombat = hasAttacked;
|
||||
public final void setCreatureAttackedThisCombat(GameEntity defender) {
|
||||
this.creatureAttackedThisCombat = defender != null;
|
||||
|
||||
if (hasAttacked) {
|
||||
this.setCreatureAttackedThisTurn(true);
|
||||
this.attacksThisTurn++;
|
||||
if (defender != null) {
|
||||
attackedThisTurn.add(defender);
|
||||
}
|
||||
}
|
||||
/**
|
||||
@@ -65,38 +63,6 @@ public class CardDamageHistory {
|
||||
public final boolean getCreatureAttackedThisCombat() {
|
||||
return this.creatureAttackedThisCombat;
|
||||
}
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>creatureAttackedThisTurn</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param b
|
||||
* a boolean.
|
||||
*/
|
||||
public final void setCreatureAttackedThisTurn(final boolean b) {
|
||||
this.creatureAttackedThisTurn = b;
|
||||
}
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>creatureAttackedThisTurn</code>.
|
||||
* </p>
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public final boolean getCreatureAttackedThisTurn() {
|
||||
return this.creatureAttackedThisTurn;
|
||||
}
|
||||
/**
|
||||
* <p>
|
||||
* Setter for the field <code>attacksThisTurn</code>.
|
||||
* </p>
|
||||
*
|
||||
* @param num
|
||||
* a integer.
|
||||
*/
|
||||
public final void setCreatureAttacksThisTurn(final int num) {
|
||||
this.attacksThisTurn = num;
|
||||
}
|
||||
/**
|
||||
* <p>
|
||||
* Getter for the field <code>attacksThisTurn</code>.
|
||||
@@ -105,7 +71,10 @@ public class CardDamageHistory {
|
||||
* @return a int.
|
||||
*/
|
||||
public final int getCreatureAttacksThisTurn() {
|
||||
return this.attacksThisTurn;
|
||||
return this.attackedThisTurn.size();
|
||||
}
|
||||
public final boolean hasAttackedThisTurn(GameEntity e) {
|
||||
return this.attackedThisTurn.contains(e);
|
||||
}
|
||||
/**
|
||||
* <p>
|
||||
@@ -307,6 +276,8 @@ public class CardDamageHistory {
|
||||
damagedThisCombat.clear();
|
||||
damagedThisTurnInCombat.clear();
|
||||
damagedThisTurn.clear();
|
||||
attackedThisTurn.clear();
|
||||
setHasBeenDealtNonCombatDamageThisTurn(false);
|
||||
}
|
||||
|
||||
public void endCombat() {
|
||||
|
||||
@@ -758,7 +758,7 @@ public class CardFactory {
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("GainThisAbility") && (sa instanceof SpellAbility)) {
|
||||
if (sa.hasParam("GainThisAbility") && sa instanceof SpellAbility) {
|
||||
SpellAbility root = ((SpellAbility) sa).getRootAbility();
|
||||
|
||||
if (root.isTrigger()) {
|
||||
@@ -803,7 +803,7 @@ public class CardFactory {
|
||||
state.setImageKey(originalState.getImageKey());
|
||||
}
|
||||
|
||||
// remove some characteristic static abilties
|
||||
// remove some characteristic static abilities
|
||||
for (StaticAbility sta : state.getStaticAbilities()) {
|
||||
if (!sta.hasParam("CharacteristicDefining")) {
|
||||
continue;
|
||||
|
||||
@@ -746,6 +746,9 @@ public class CardFactoryUtil {
|
||||
}
|
||||
String abStr = "DB$ PutCounter | Defined$ Self | CounterType$ " + splitkw[1]
|
||||
+ " | ETB$ True | CounterNum$ " + amount;
|
||||
if (splitkw[1].startsWith("EACH")) {
|
||||
abStr = abStr.replace("CounterType$ EACH ", "CounterTypes$ ");
|
||||
}
|
||||
|
||||
SpellAbility sa = AbilityFactory.getAbility(abStr, card);
|
||||
setupETBReplacementAbility(sa);
|
||||
@@ -863,7 +866,7 @@ public class CardFactoryUtil {
|
||||
inst.addTrigger(bushidoTrigger1);
|
||||
inst.addTrigger(bushidoTrigger2);
|
||||
} else if (keyword.equals("Cascade")) {
|
||||
final StringBuilder trigScript = new StringBuilder("Mode$ SpellCast | ValidCard$ Card.Self" +
|
||||
final StringBuilder trigScript = new StringBuilder("Mode$ SpellCast | ValidCard$ Card.Self | TriggerZones$ Stack" +
|
||||
" | Secondary$ True | TriggerDescription$ Cascade - CARDNAME");
|
||||
|
||||
final String abString = "DB$ DigUntil | Defined$ You | Amount$ 1 | Valid$ Card.nonLand+cmcLTCascadeX" +
|
||||
@@ -956,7 +959,7 @@ public class CardFactoryUtil {
|
||||
inst.addTrigger(parsedTrigger);
|
||||
inst.addTrigger(parsedTrigReturn);
|
||||
} else if (keyword.startsWith("Casualty")) {
|
||||
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ Casualty | Secondary$ True";
|
||||
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ Casualty | TriggerZones$ Stack | Secondary$ True";
|
||||
String abString = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ 1 | MayChooseTarget$ True";
|
||||
String[] k = keyword.split(":");
|
||||
if (k.length > 2) {
|
||||
@@ -969,7 +972,7 @@ public class CardFactoryUtil {
|
||||
|
||||
inst.addTrigger(casualtyTrigger);
|
||||
} else if (keyword.equals("Conspire")) {
|
||||
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ Conspire | Secondary$ True | TriggerDescription$ Copy CARDNAME if its conspire cost was paid";
|
||||
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ Conspire | TriggerZones$ Stack | Secondary$ True | TriggerDescription$ Copy CARDNAME if its conspire cost was paid";
|
||||
final String abString = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ 1 | MayChooseTarget$ True";
|
||||
|
||||
final Trigger conspireTrigger = TriggerHandler.parseTrigger(trigScript, card, intrinsic);
|
||||
@@ -1034,7 +1037,7 @@ public class CardFactoryUtil {
|
||||
parsedTrigger.setOverridingAbility(delayTrigSA);
|
||||
inst.addTrigger(parsedTrigger);
|
||||
} else if (keyword.equals("Demonstrate")) {
|
||||
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | TriggerDescription$ Demonstrate (" + inst.getReminderText() + ")";
|
||||
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | TriggerZones$ Stack | TriggerDescription$ Demonstrate (" + inst.getReminderText() + ")";
|
||||
final String youCopyStr = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | MayChooseTarget$ True | Optional$ True | RememberCopies$ True | IgnoreFreeze$ True";
|
||||
final String chooseOppStr = "DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | ConditionDefined$ Remembered | ConditionPresent$ Spell";
|
||||
final String oppCopyStr = "DB$ CopySpellAbility | Controller$ ChosenPlayer | Defined$ TriggeredSpellAbility | MayChooseTarget$ True | ConditionDefined$ Remembered | ConditionPresent$ Spell";
|
||||
@@ -1223,7 +1226,7 @@ public class CardFactoryUtil {
|
||||
|
||||
inst.addTrigger(trigger);
|
||||
} else if (keyword.startsWith("Gravestorm")) {
|
||||
String trigStr = "Mode$ SpellCast | ValidCard$ Card.Self | TriggerDescription$ Gravestorm (" + inst.getReminderText() + ")";
|
||||
String trigStr = "Mode$ SpellCast | ValidCard$ Card.Self | TriggerZones$ Stack | TriggerDescription$ Gravestorm (" + inst.getReminderText() + ")";
|
||||
String copyStr = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ GravestormCount | MayChooseTarget$ True";
|
||||
|
||||
SpellAbility copySa = AbilityFactory.getAbility(copyStr, card);
|
||||
@@ -1338,7 +1341,7 @@ public class CardFactoryUtil {
|
||||
sb.append("TriggerDescription$ Hideaway ").append(n).append(" (").append(inst.getReminderText()).append(")");
|
||||
final Trigger hideawayTrigger = TriggerHandler.parseTrigger(sb.toString(), card, intrinsic);
|
||||
|
||||
String hideawayDig = "DB$ Dig | Defined$ You | DigNum$ " + n + " | DestinationZone$ Exile | ExileFaceDown$ True | RememberChanged$ True";
|
||||
String hideawayDig = "DB$ Dig | Defined$ You | DigNum$ " + n + " | DestinationZone$ Exile | ExileFaceDown$ True | RememberChanged$ True | RestRandomOrder$ True";
|
||||
String hideawayEffect = "DB$ Effect | StaticAbilities$ STHideawayEffectLookAtCard | ForgetOnMoved$ Exile | RememberObjects$ Remembered | Duration$ Permanent";
|
||||
|
||||
String lookAtCard = "Mode$ Continuous | Affected$ Card.IsRemembered | MayLookAt$ EffectSourceController | EffectZone$ Command | AffectedZone$ Exile | Description$ Any player who has controlled the permanent that exiled this card may look at this card in the exile zone.";
|
||||
@@ -1505,38 +1508,15 @@ public class CardFactoryUtil {
|
||||
final String actualTrigger = "Mode$ Attacks | ValidCard$ Card.Self | Secondary$ True"
|
||||
+ " | TriggerDescription$ Myriad (" + inst.getReminderText() + ")";
|
||||
|
||||
final String repeatStr = "DB$ RepeatEach | RepeatPlayers$ OpponentsOtherThanDefendingPlayer | ChangeZoneTable$ True";
|
||||
|
||||
final String copyStr = "DB$ CopyPermanent | Defined$ Self | TokenTapped$ True | Optional$ True | TokenAttacking$ Remembered"
|
||||
+ " | ChoosePlayerOrPlaneswalker$ True | ImprintTokens$ True";
|
||||
+ "| ForEach$ OpponentsOtherThanDefendingPlayer | ChoosePlayerOrPlaneswalker$ True | AtEOT$ ExileCombat | CleanupForEach$ True";
|
||||
|
||||
final String delTrigStr = "DB$ DelayedTrigger | Mode$ Phase | Phase$ EndCombat | RememberObjects$ Imprinted"
|
||||
+ " | TriggerDescription$ Exile the tokens at end of combat.";
|
||||
|
||||
final String exileStr = "DB$ ChangeZone | Defined$ DelayTriggerRemembered | Origin$ Battlefield | Destination$ Exile";
|
||||
|
||||
final String cleanupStr = "DB$ Cleanup | ClearImprinted$ True";
|
||||
|
||||
SpellAbility repeatSA = AbilityFactory.getAbility(repeatStr, card);
|
||||
|
||||
AbilitySub copySA = (AbilitySub) AbilityFactory.getAbility(copyStr, card);
|
||||
repeatSA.setAdditionalAbility("RepeatSubAbility", copySA);
|
||||
|
||||
AbilitySub delTrigSA = (AbilitySub) AbilityFactory.getAbility(delTrigStr, card);
|
||||
|
||||
AbilitySub exileSA = (AbilitySub) AbilityFactory.getAbility(exileStr, card);
|
||||
delTrigSA.setAdditionalAbility("Execute", exileSA);
|
||||
|
||||
AbilitySub cleanupSA = (AbilitySub) AbilityFactory.getAbility(cleanupStr, card);
|
||||
delTrigSA.setSubAbility(cleanupSA);
|
||||
|
||||
repeatSA.setSubAbility(delTrigSA);
|
||||
final SpellAbility copySA = AbilityFactory.getAbility(copyStr, card);
|
||||
copySA.setIntrinsic(intrinsic);
|
||||
|
||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, intrinsic);
|
||||
parsedTrigger.setOverridingAbility(copySA);
|
||||
|
||||
repeatSA.setIntrinsic(intrinsic);
|
||||
|
||||
parsedTrigger.setOverridingAbility(repeatSA);
|
||||
inst.addTrigger(parsedTrigger);
|
||||
} else if (keyword.equals("Nightbound")) {
|
||||
// Set Night when it's Neither
|
||||
@@ -1685,7 +1665,7 @@ public class CardFactoryUtil {
|
||||
|
||||
inst.addTrigger(myTrigger);
|
||||
} else if (keyword.startsWith("Replicate")) {
|
||||
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ ReplicateAmount | Secondary$ True | TriggerDescription$ Copy CARDNAME for each time you paid its replicate cost";
|
||||
final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ ReplicateAmount | Secondary$ True | TriggerDescription$ Copy CARDNAME for each time you paid its replicate cost.";
|
||||
final String abString = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ ReplicateAmount | MayChooseTarget$ True";
|
||||
|
||||
final Trigger replicateTrigger = TriggerHandler.parseTrigger(trigScript, card, intrinsic);
|
||||
@@ -1701,8 +1681,7 @@ public class CardFactoryUtil {
|
||||
final String actualTrigger = "Mode$ SpellCast | ValidCard$ Card.Self | OptionalDecider$ You | "
|
||||
+ " Secondary$ True | TriggerDescription$ Ripple " + num + " - CARDNAME";
|
||||
|
||||
final String abString = "DB$ Dig | NoMove$ True | DigNum$ " + num +
|
||||
" | Reveal$ True | RememberRevealed$ True";
|
||||
final String abString = "DB$ PeekAndReveal | PeekAmount$ " + num + " | RememberRevealed$ True";
|
||||
|
||||
final String dbCast = "DB$ Play | Valid$ Card.IsRemembered+sameName | " +
|
||||
"ValidZone$ Library | WithoutManaCost$ True | Optional$ True | " +
|
||||
@@ -1805,7 +1784,7 @@ public class CardFactoryUtil {
|
||||
|
||||
inst.addTrigger(parsedTrigger);
|
||||
} else if (keyword.equals("Storm")) {
|
||||
final String actualTrigger = "Mode$ SpellCast | ValidCard$ Card.Self | Secondary$ True"
|
||||
final String actualTrigger = "Mode$ SpellCast | ValidCard$ Card.Self | TriggerZones$ Stack | Secondary$ True"
|
||||
+ "| TriggerDescription$ Storm (" + inst.getReminderText() + ")";
|
||||
|
||||
String effect = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ StormCount | MayChooseTarget$ True";
|
||||
@@ -2810,13 +2789,14 @@ public class CardFactoryUtil {
|
||||
String valid = k.length > 2 && !k[2].isEmpty() ? k[2] : "Creature.YouCtrl";
|
||||
String vstr = k.length > 3 && !k[3].isEmpty() ? k[3] : "creature";
|
||||
String extra = k.length > 4 ? k[4] : "";
|
||||
boolean altCost = extra.contains("AlternateCost");
|
||||
String extraDesc = k.length > 5 ? k[5] : "";
|
||||
// Create attach ability string
|
||||
final StringBuilder abilityStr = new StringBuilder();
|
||||
abilityStr.append("AB$ Attach | Cost$ ");
|
||||
abilityStr.append(equipCost);
|
||||
abilityStr.append("| ValidTgts$ ").append(valid);
|
||||
abilityStr.append("| TgtPrompt$ Select target ").append(vstr).append(" you control ");
|
||||
abilityStr.append(" | TgtPrompt$ Select target ").append(vstr).append(" you control ");
|
||||
abilityStr.append("| SorcerySpeed$ True | Equip$ True | AILogic$ Pump | IsPresent$ Equipment.Self+nonCreature ");
|
||||
// add AttachAi for some special cards
|
||||
if (card.hasSVar("AttachAi")) {
|
||||
@@ -2827,19 +2807,23 @@ public class CardFactoryUtil {
|
||||
abilityStr.append(" ").append(vstr);
|
||||
}
|
||||
Cost cost = new Cost(equipCost, true);
|
||||
if (!cost.isOnlyManaCost()) { //Something other than a mana cost
|
||||
if (!cost.isOnlyManaCost() || altCost) { //Something other than a mana cost
|
||||
abilityStr.append("—");
|
||||
} else {
|
||||
abilityStr.append(" ");
|
||||
}
|
||||
abilityStr.append("| CostDesc$ ").append(cost.toSimpleString()).append(" ");
|
||||
if (!altCost) {
|
||||
abilityStr.append("| CostDesc$ ").append(cost.toSimpleString()).append(" ");
|
||||
}
|
||||
abilityStr.append("| SpellDescription$ ");
|
||||
if (!extraDesc.isEmpty()) {
|
||||
abilityStr.append(". ").append(extraDesc).append(". ");
|
||||
}
|
||||
abilityStr.append("(").append(inst.getReminderText()).append(")");
|
||||
if (!altCost) {
|
||||
abilityStr.append("(").append(inst.getReminderText()).append(")");
|
||||
}
|
||||
if (!extra.isEmpty()) {
|
||||
abilityStr.append("| ").append(extra);
|
||||
abilityStr.append(" | ").append(extra);
|
||||
}
|
||||
// instantiate attach ability
|
||||
final SpellAbility newSA = AbilityFactory.getAbility(abilityStr.toString(), card);
|
||||
@@ -3207,52 +3191,31 @@ public class CardFactoryUtil {
|
||||
final String[] k = keyword.split(":");
|
||||
final String manacost = k[1];
|
||||
|
||||
String effect = "AB$ RepeatEach | Cost$ " + manacost + " ExileFromGrave<1/CARDNAME> " +
|
||||
"| ActivationZone$ Graveyard | ClearRememberedBeforeLoop$ True | RepeatPlayers$ Opponent" +
|
||||
"| PrecostDesc$ Encore | CostDesc$ " + ManaCostParser.parse(manacost) +
|
||||
final String effect = "AB$ CopyPermanent | Cost$ " + manacost + " ExileFromGrave<1/CARDNAME> | ActivationZone$ Graveyard" +
|
||||
"| Defined$ Self | PumpKeywords$ Haste | RememberTokens$ True | ForEach$ Opponent" +
|
||||
"| AtEOT$ Sacrifice | PrecostDesc$ Encore | CostDesc$ " + ManaCostParser.parse(manacost) +
|
||||
"| SpellDescription$ (" + inst.getReminderText() + ")";
|
||||
|
||||
final String copyStr = "DB$ CopyPermanent | Defined$ Self | ImprintTokens$ True " +
|
||||
"| AddKeywords$ Haste | RememberTokens$ True | TokenRemembered$ Player.IsRemembered " +
|
||||
"| AddStaticAbilities$ MustAttack";
|
||||
|
||||
final String pumpStr = "DB$ Animate | Defined$ Creature.IsRemembered | staticAbilities$ AttackChosen ";
|
||||
|
||||
final String attackStaticStr = "Mode$ MustAttack | ValidCreature$ Card.Self | MustAttack$ Remembered" +
|
||||
" | Description$ This token copy attacks that opponent this turn if able.";
|
||||
|
||||
final String pumpcleanStr = "DB$ Cleanup | ForgetDefined$ RememberedCard";
|
||||
|
||||
final String delTrigStr = "DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | RememberObjects$ Imprinted " +
|
||||
"| StackDescription$ None | TriggerDescription$ Sacrifice them at the beginning of the next end step.";
|
||||
|
||||
final String sacStr = "DB$ SacrificeAll | Defined$ DelayTriggerRememberedLKI | Controller$ You";
|
||||
|
||||
final String cleanupStr = "DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True";
|
||||
|
||||
final SpellAbility sa = AbilityFactory.getAbility(effect, card);
|
||||
|
||||
final String repeatStr = "DB$ RepeatEach | DefinedCards$ Remembered | UseImprinted$ True";
|
||||
final AbilitySub repeatSub = (AbilitySub) AbilityFactory.getAbility(repeatStr, card);
|
||||
sa.setSubAbility(repeatSub);
|
||||
|
||||
final String effectStr = "DB$ Effect | RememberObjects$ Imprinted,ImprintedRemembered | ExileOnMoved$ Battlefield | StaticAbilities$ AttackChosen";
|
||||
final AbilitySub effectSub = (AbilitySub) AbilityFactory.getAbility(effectStr, card);
|
||||
repeatSub.setAdditionalAbility("RepeatSubAbility", effectSub);
|
||||
|
||||
final String attackStaticStr = "Mode$ MustAttack | ValidCreature$ Card.IsRemembered | MustAttack$ RememberedPlayer" +
|
||||
" | Description$ This token copy attacks that opponent this turn if able.";
|
||||
effectSub.setSVar("AttackChosen", attackStaticStr);
|
||||
|
||||
final String cleanStr = "DB$ Cleanup | Defined$ Imprinted | ForgetDefined$ Remembered";
|
||||
final AbilitySub cleanSub = (AbilitySub) AbilityFactory.getAbility(cleanStr, card);
|
||||
effectSub.setSubAbility(cleanSub);
|
||||
|
||||
sa.setIntrinsic(intrinsic);
|
||||
inst.addSpellAbility(sa);
|
||||
|
||||
AbilitySub copySA = (AbilitySub) AbilityFactory.getAbility(copyStr, card);
|
||||
sa.setAdditionalAbility("RepeatSubAbility", copySA);
|
||||
|
||||
AbilitySub pumpSA = (AbilitySub) AbilityFactory.getAbility(pumpStr, card);
|
||||
copySA.setSubAbility(pumpSA);
|
||||
|
||||
sa.setSVar("MustAttack", attackStaticStr);
|
||||
|
||||
AbilitySub pumpcleanSA = (AbilitySub) AbilityFactory.getAbility(pumpcleanStr, card);
|
||||
pumpSA.setSubAbility(pumpcleanSA);
|
||||
|
||||
AbilitySub delTrigSA = (AbilitySub) AbilityFactory.getAbility(delTrigStr, card);
|
||||
sa.setSubAbility(delTrigSA);
|
||||
|
||||
AbilitySub sacSA = (AbilitySub) AbilityFactory.getAbility(sacStr, card);
|
||||
delTrigSA.setAdditionalAbility("Execute", sacSA);
|
||||
|
||||
AbilitySub cleanupSA = (AbilitySub) AbilityFactory.getAbility(cleanupStr, card);
|
||||
delTrigSA.setSubAbility(cleanupSA);
|
||||
} else if (keyword.startsWith("Spectacle")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final Cost cost = new Cost(k[1], false);
|
||||
@@ -3260,8 +3223,7 @@ public class CardFactoryUtil {
|
||||
|
||||
newSA.setAlternativeCost(AlternativeCost.Spectacle);
|
||||
|
||||
String desc = "Spectacle " + cost.toSimpleString() + " (" + inst.getReminderText()
|
||||
+ ")";
|
||||
String desc = "Spectacle " + cost.toSimpleString() + " (" + inst.getReminderText() + ")";
|
||||
newSA.setDescription(desc);
|
||||
|
||||
newSA.setIntrinsic(intrinsic);
|
||||
@@ -3399,9 +3361,8 @@ public class CardFactoryUtil {
|
||||
// tapXType has a special check for withTotalPower, and NEEDS it to be "+withTotalPowerGE"
|
||||
String effect = "AB$ Animate | Cost$ tapXType<Any/Creature.Other+withTotalPowerGE" + power + "> | " +
|
||||
"CostDesc$ Crew " + power + " (Tap any number of creatures you control with total power " + power +
|
||||
" or more: | Crew$ True | Secondary$ True | Defined$ Self | Types$ Creature,Artifact | " +
|
||||
"RemoveCardTypes$ True | StackDescription$ SpellDescription | SpellDescription$ CARDNAME becomes" +
|
||||
" an artifact creature until end of turn.)";
|
||||
" or more: | Crew$ True | Secondary$ True | Defined$ Self | Types$ Artifact,Creature | " +
|
||||
"SpellDescription$ CARDNAME becomes an artifact creature until end of turn.)";
|
||||
|
||||
final SpellAbility sa = AbilityFactory.getAbility(effect, card);
|
||||
sa.setIntrinsic(intrinsic);
|
||||
|
||||
@@ -251,11 +251,11 @@ public final class CardPredicates {
|
||||
};
|
||||
}
|
||||
|
||||
public static final Predicate<Card> canBeAttached(final Card aura) {
|
||||
public static final Predicate<Card> canBeAttached(final Card aura, final SpellAbility sa) {
|
||||
return new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.canBeAttached(aura);
|
||||
return c.canBeAttached(aura, sa);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -733,13 +733,6 @@ public final class CardPredicates {
|
||||
}
|
||||
|
||||
public static class Accessors {
|
||||
public static final Function<Card, Integer> fnGetDefense = new Function<Card, Integer>() {
|
||||
@Override
|
||||
public Integer apply(Card a) {
|
||||
return a.getNetToughness();
|
||||
}
|
||||
};
|
||||
|
||||
public static final Function<Card, Integer> fnGetNetPower = new Function<Card, Integer>() {
|
||||
@Override
|
||||
public Integer apply(Card a) {
|
||||
@@ -767,6 +760,13 @@ public final class CardPredicates {
|
||||
return a.getCMC();
|
||||
}
|
||||
};
|
||||
|
||||
public static final Function<Card, String> fnGetNetName = new Function<Card, String>() {
|
||||
@Override
|
||||
public String apply(Card a) {
|
||||
return a.getName();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user