mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-14 09:48:02 +00:00
Compare commits
1052 Commits
linkedAbil
...
forge-1.6.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c1fe344e1 | ||
|
|
3161033675 | ||
|
|
ede000f264 | ||
|
|
4e99f80f56 | ||
|
|
5c807f1ee7 | ||
|
|
25f60fb237 | ||
|
|
8cc48fdfd7 | ||
|
|
42da203998 | ||
|
|
74249924da | ||
|
|
3c0d8660c7 | ||
|
|
1d3a277485 | ||
|
|
35acb80ee9 | ||
|
|
8ddc803852 | ||
|
|
a851fc850c | ||
|
|
05fdaf2482 | ||
|
|
a11a3007a1 | ||
|
|
1cf84f69cc | ||
|
|
d6e6f86dcc | ||
|
|
8f0dc73931 | ||
|
|
0108dc9e1d | ||
|
|
c128be1816 | ||
|
|
75a9795a4d | ||
|
|
d3ebc9497f | ||
|
|
637234870c | ||
|
|
8e8b3eece8 | ||
|
|
4e636857c2 | ||
|
|
9f581d0bc1 | ||
|
|
fd39200fa6 | ||
|
|
8c558a28d1 | ||
|
|
77e1621932 | ||
|
|
712724c29a | ||
|
|
61a483f53f | ||
|
|
6d2334e94b | ||
|
|
447fc7b8f3 | ||
|
|
ebd9969228 | ||
|
|
27480926b7 | ||
|
|
1816bfaed7 | ||
|
|
17fa6f550f | ||
|
|
af00bc605e | ||
|
|
a7a9985629 | ||
|
|
0243c02537 | ||
|
|
c9657558dd | ||
|
|
d04ef252ab | ||
|
|
8053e16663 | ||
|
|
bd78906c6e | ||
|
|
9e7a202e5c | ||
|
|
2ddddd6e97 | ||
|
|
2e312cf27a | ||
|
|
ebc2708af6 | ||
|
|
3c2216481e | ||
|
|
db6b524c5a | ||
|
|
e6240a0ea8 | ||
|
|
da4e2bd03b | ||
|
|
8134908a81 | ||
|
|
ecaaf3a951 | ||
|
|
23a2fc2fd4 | ||
|
|
55e58f29e8 | ||
|
|
b689a960b4 | ||
|
|
dd220ec270 | ||
|
|
4771088676 | ||
|
|
096d0e6a99 | ||
|
|
ac4e03082f | ||
|
|
b04461ac10 | ||
|
|
dce11aa70d | ||
|
|
9f64006739 | ||
|
|
dfbbf49d2e | ||
|
|
0e7d730b1f | ||
|
|
c6d779ff08 | ||
|
|
fe25c7d2c4 | ||
|
|
ffc24faa5a | ||
|
|
f34f7810ce | ||
|
|
b19806eea2 | ||
|
|
e527c2f9a6 | ||
|
|
ae345b6468 | ||
|
|
929de7181c | ||
|
|
d4eaba43f6 | ||
|
|
180a90e0a1 | ||
|
|
faea325473 | ||
|
|
2fa38e2ecf | ||
|
|
0d030976bc | ||
|
|
b66d73162a | ||
|
|
ca9f20281a | ||
|
|
dd5a2f16b4 | ||
|
|
38ea1fad46 | ||
|
|
684cb4115d | ||
|
|
b996ce4720 | ||
|
|
e3dba72bf2 | ||
|
|
8608a0e438 | ||
|
|
5fdd62358d | ||
|
|
cdbbb3eb4b | ||
|
|
3a5ec9e846 | ||
|
|
a4591b5254 | ||
|
|
1c7a3b92ab | ||
|
|
2735f598ff | ||
|
|
02ae6282ba | ||
|
|
e585239866 | ||
|
|
886fcf3d2f | ||
|
|
dbcb8b9a71 | ||
|
|
a5e65c9563 | ||
|
|
9f21108218 | ||
|
|
4c98a1a5fa | ||
|
|
c893a01bb8 | ||
|
|
6207db6bd8 | ||
|
|
dff30a09c6 | ||
|
|
62c9c0e9da | ||
|
|
5c96a624f2 | ||
|
|
1e814caec4 | ||
|
|
f556d5a24d | ||
|
|
fcbd98d3e6 | ||
|
|
2b3522205d | ||
|
|
185598ab9c | ||
|
|
aa40b9c64a | ||
|
|
0750ae52bb | ||
|
|
9d0b1128e0 | ||
|
|
b54b6e83c2 | ||
|
|
5ee8f949e8 | ||
|
|
978e6b4568 | ||
|
|
1a20722fdb | ||
|
|
636ad404c7 | ||
|
|
06f8944d2b | ||
|
|
4645e32ab7 | ||
|
|
86be83ff44 | ||
|
|
c4b8d52abc | ||
|
|
da9978a063 | ||
|
|
2655b4a626 | ||
|
|
728191ca1e | ||
|
|
f1516060c9 | ||
|
|
6db9e0b7d1 | ||
|
|
de063350d6 | ||
|
|
3e7a84759e | ||
|
|
8306da261c | ||
|
|
18683d713a | ||
|
|
cb4e0be196 | ||
|
|
118c79c572 | ||
|
|
91187af7f7 | ||
|
|
03c9a12826 | ||
|
|
3583bc1e11 | ||
|
|
6d83c439a8 | ||
|
|
fae435a29f | ||
|
|
139160c32d | ||
|
|
e16a80e822 | ||
|
|
0a79fa4c20 | ||
|
|
44d45cef91 | ||
|
|
566768bbd8 | ||
|
|
a97c18a16b | ||
|
|
4997ddc0e2 | ||
|
|
e044abf20c | ||
|
|
296ddf63ed | ||
|
|
c7dd68749d | ||
|
|
9724786d8d | ||
|
|
d9dae5be6e | ||
|
|
bfa75b4042 | ||
|
|
61763c9bb8 | ||
|
|
90d4eb0846 | ||
|
|
23799411e6 | ||
|
|
01b7aa1818 | ||
|
|
d898ca7987 | ||
|
|
8773ae34fb | ||
|
|
928f5457d8 | ||
|
|
5131d9ed96 | ||
|
|
888d49a43f | ||
|
|
b9897ecb00 | ||
|
|
93ce8dd188 | ||
|
|
eaa84f07ea | ||
|
|
4db0f4742b | ||
|
|
15bc4e4d1b | ||
|
|
2ba76439e1 | ||
|
|
51362e8290 | ||
|
|
f0414c1ef0 | ||
|
|
1ca5609d1f | ||
|
|
f9620ec816 | ||
|
|
385fcbe4e8 | ||
|
|
34e3bd2dc1 | ||
|
|
58c738e414 | ||
|
|
7e6496551c | ||
|
|
f8e3032671 | ||
|
|
103bffa681 | ||
|
|
e3ba05b612 | ||
|
|
f8ef3ef186 | ||
|
|
06c742ca2d | ||
|
|
4787a4c6df | ||
|
|
defc9f41f4 | ||
|
|
35024b49ea | ||
|
|
af22f0e04f | ||
|
|
7e31ee6d25 | ||
|
|
40e62f4665 | ||
|
|
b68e425d84 | ||
|
|
4f9f755e05 | ||
|
|
4bd141d3c0 | ||
|
|
0f4a0c58b8 | ||
|
|
8349cb6df1 | ||
|
|
6c32809b1a | ||
|
|
029142fbd3 | ||
|
|
39394f77fb | ||
|
|
e76626427d | ||
|
|
c4223280dd | ||
|
|
858b911d4b | ||
|
|
da55f6acfe | ||
|
|
5e1624a41a | ||
|
|
0b5048cf60 | ||
|
|
22aca4e448 | ||
|
|
eba20e3c62 | ||
|
|
6a1ed1de8d | ||
|
|
6bee902800 | ||
|
|
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 | ||
|
|
44b839e4f6 | ||
|
|
9a0a9ab7fc | ||
|
|
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.51</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.51</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.51</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()) {
|
||||
@@ -1839,7 +1840,6 @@ 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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -169,6 +169,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.StoreSVar, StoreSVarAi.class)
|
||||
.put(ApiType.Subgame, AlwaysPlayAi.class)
|
||||
.put(ApiType.Surveil, SurveilAi.class)
|
||||
.put(ApiType.TakeInitiative, AlwaysPlayAi.class)
|
||||
.put(ApiType.Tap, TapAi.class)
|
||||
.put(ApiType.TapAll, TapAllAi.class)
|
||||
.put(ApiType.TapOrUntap, TapOrUntapAi.class)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -752,14 +752,16 @@ public class CountersPutAi extends CountersAi {
|
||||
final int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
int left = amount;
|
||||
final String[] types;
|
||||
String type = "";
|
||||
if (sa.hasParam("CounterType")) {
|
||||
// TODO some cards let you choose types, should check each
|
||||
types = sa.getParam("CounterType").split(",");
|
||||
} else {
|
||||
type = types[0];
|
||||
} else if (sa.hasParam("CounterTypes")) {
|
||||
// all types will be added
|
||||
types = sa.getParam("CounterTypes").split(",");
|
||||
type = types[0];
|
||||
}
|
||||
final String type = types[0];
|
||||
|
||||
if (!sa.usesTargeting()) {
|
||||
// No target. So must be defined
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -31,6 +31,11 @@ public class ImmediateTriggerAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
// always add to stack, targeting happens after payment
|
||||
if (mandatory) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String logic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
SpellAbility trigsa = sa.getAdditionalAbility("Execute");
|
||||
@@ -45,11 +50,7 @@ public class ImmediateTriggerAi extends SpellAbilityAi {
|
||||
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
|
||||
trigsa.setActivatingPlayer(ai);
|
||||
|
||||
if (!sa.hasParam("OptionalDecider")) {
|
||||
return aic.doTrigger(trigsa, true);
|
||||
} else {
|
||||
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
|
||||
}
|
||||
return aic.doTrigger(trigsa, !"You".equals(sa.getParamOrDefault("OptionalDecider", "You")));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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,6 +41,28 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.51</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());
|
||||
|
||||
@@ -214,8 +214,15 @@ public final class CardRules implements ICardCharacteristics {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEnterableDungeon() {
|
||||
if (mainPart.getOracleText().contains("You can't enter this dungeon unless")) {
|
||||
return false;
|
||||
}
|
||||
return getType().isDungeon();
|
||||
}
|
||||
|
||||
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 +239,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.
|
||||
*
|
||||
|
||||
@@ -33,6 +33,7 @@ import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
@@ -71,6 +72,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
public final String pluralName;
|
||||
private static Map<String, CoreType> stringToCoreType = EnumUtils.getEnumMap(CoreType.class);
|
||||
private static final Set<String> allCoreTypeNames = stringToCoreType.keySet();
|
||||
public static final Set<CoreType> spellTypes = ImmutableSet.of(Instant, Sorcery);
|
||||
|
||||
public static CoreType getEnum(String name) {
|
||||
return stringToCoreType.get(name);
|
||||
@@ -535,7 +537,8 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
newType = new CardType(CardType.this);
|
||||
|
||||
if (ct.isRemoveCardTypes()) {
|
||||
newType.coreTypes.clear();
|
||||
// 205.1a However, an object with either the instant or sorcery card type retains that type.
|
||||
newType.coreTypes.retainAll(CoreType.spellTypes);
|
||||
}
|
||||
if (ct.isRemoveSuperTypes()) {
|
||||
newType.supertypes.clear();
|
||||
@@ -612,6 +615,9 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
if (!isPlaneswalker()) {
|
||||
Iterables.removeIf(subtypes, Predicates.IS_WALKER_TYPE);
|
||||
}
|
||||
if (!isDungeon()) {
|
||||
Iterables.removeIf(subtypes, Predicates.IS_DUNGEON_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -787,6 +793,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
public static final Set<String> ENCHANTMENT_TYPES = Sets.newHashSet();
|
||||
public static final Set<String> ARTIFACT_TYPES = Sets.newHashSet();
|
||||
public static final Set<String> WALKER_TYPES = Sets.newHashSet();
|
||||
public static final Set<String> DUNGEON_TYPES = Sets.newHashSet();
|
||||
|
||||
// singular -> plural
|
||||
public static final BiMap<String,String> pluralTypes = HashBiMap.create();
|
||||
@@ -846,6 +853,12 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
return CardType.isAPlaneswalkerType(input);
|
||||
}
|
||||
};
|
||||
public static Predicate<String> IS_DUNGEON_TYPE = new Predicate<String>() {
|
||||
@Override
|
||||
public boolean apply(String input) {
|
||||
return CardType.isADungeonType(input);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
///////// Utility methods
|
||||
@@ -878,6 +891,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
sortedSubTypes.addAll(Constant.ENCHANTMENT_TYPES);
|
||||
sortedSubTypes.addAll(Constant.ARTIFACT_TYPES);
|
||||
sortedSubTypes.addAll(Constant.WALKER_TYPES);
|
||||
sortedSubTypes.addAll(Constant.DUNGEON_TYPES);
|
||||
Collections.sort(sortedSubTypes);
|
||||
}
|
||||
return sortedSubTypes;
|
||||
@@ -933,6 +947,9 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
return (Constant.SPELL_TYPES.contains(cardType));
|
||||
}
|
||||
|
||||
public static boolean isADungeonType(final String cardType) {
|
||||
return (Constant.DUNGEON_TYPES.contains(cardType));
|
||||
}
|
||||
/**
|
||||
* If the input is a plural type, return the corresponding singular form.
|
||||
* Otherwise, simply return the input.
|
||||
|
||||
@@ -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()
|
||||
|| b.getRules().hasKeyword("Choose a Background") && a.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.51</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
@@ -259,6 +259,18 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
}
|
||||
if (params.containsKey("Revolt")) {
|
||||
if ("True".equalsIgnoreCase(params.get("Revolt")) != hostController.hasRevolt()) return false;
|
||||
else if ("None".equalsIgnoreCase(params.get("Revolt"))) {
|
||||
boolean none = true;
|
||||
for (Player p : game.getRegisteredPlayers()) {
|
||||
if (p.hasRevolt()) {
|
||||
none = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!none) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (params.containsKey("Desert")) {
|
||||
if ("True".equalsIgnoreCase(params.get("Desert")) != hostController.hasDesert()) return false;
|
||||
|
||||
@@ -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,15 +117,13 @@ 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();
|
||||
private Map<Card, Integer> facedownWhileCasting = Maps.newHashMap();
|
||||
|
||||
private Player monarch = null;
|
||||
private Player initiative = null;
|
||||
private Player monarchBeginTurn = null;
|
||||
private Player startingPlayer;
|
||||
|
||||
@@ -176,26 +174,11 @@ public class Game {
|
||||
this.monarchBeginTurn = monarchBeginTurn;
|
||||
}
|
||||
|
||||
public Map<Player, PlayerCollection> getPlayersAttackedThisTurn() {
|
||||
return attackedThisTurn;
|
||||
public Player getHasInitiative() {
|
||||
return initiative;
|
||||
}
|
||||
|
||||
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 void setHasInitiative(final Player p ) {
|
||||
initiative = p;
|
||||
}
|
||||
|
||||
public CardCollectionView getLastStateBattlefield() {
|
||||
@@ -871,6 +854,18 @@ public class Game {
|
||||
}
|
||||
}
|
||||
|
||||
if (p.hasInitiative()) {
|
||||
// The third way to take the initiative is if the player who currently has the initiative leaves the game.
|
||||
// When that happens, the player whose turn it is takes the initiative.
|
||||
// If the player who has the initiative leaves the game on their own turn,
|
||||
// or the active player left the game at the same time, the next player in turn order takes the initiative.
|
||||
if (p.equals(getPhaseHandler().getPlayerTurn())) {
|
||||
getAction().takeInitiative(getNextPlayerAfter(p), null);
|
||||
} else {
|
||||
getAction().takeInitiative(getPhaseHandler().getPlayerTurn(), null);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove leftover items from
|
||||
getStack().removeInstancesControlledBy(p);
|
||||
|
||||
@@ -1093,8 +1088,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);
|
||||
@@ -2168,6 +2203,32 @@ public class GameAction {
|
||||
game.getTriggerHandler().runTrigger(TriggerType.BecomeMonarch, runParams, false);
|
||||
}
|
||||
|
||||
public void takeInitiative(final Player p, final String set) {
|
||||
final Player previous = game.getHasInitiative();
|
||||
if (p == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!p.equals(previous)) {
|
||||
if (previous != null) {
|
||||
previous.removeInitiativeEffect();
|
||||
}
|
||||
|
||||
if (p.hasLost()) { // the person who should take initiative is gone, it goes to next player
|
||||
takeInitiative(game.getNextPlayerAfter(p), set);
|
||||
}
|
||||
|
||||
game.setHasInitiative(p);
|
||||
p.createInitiativeEffect(set);
|
||||
}
|
||||
|
||||
// You can take the initiative even if you already have it
|
||||
// Run triggers
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, p);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.TakesInitiative, runParams, false);
|
||||
}
|
||||
|
||||
// Make scry an action function so that it can be used for mulligans (with a null cause)
|
||||
// Assumes that the list of players is in APNAP order, which should be the case
|
||||
// Optional here as well to handle the way that mulligans do the choice
|
||||
@@ -2382,7 +2443,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 +2470,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
|
||||
*/
|
||||
|
||||
@@ -94,7 +94,9 @@ public class GameEntityCounterTable extends ForwardingTable<Optional<Player>, Ga
|
||||
for (Map<CounterType, Integer> cm : gm.getValue().values()) {
|
||||
Integer old = ObjectUtils.firstNonNull(result.get(gm.getKey()), 0);
|
||||
Integer v = ObjectUtils.firstNonNull(cm.get(type), 0);
|
||||
result.put(gm.getKey(), old + v);
|
||||
if (old + v > 0) {
|
||||
result.put(gm.getKey(), old + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,13 +28,11 @@ 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."),
|
||||
onlyOneBlockerPerOpponent ("Each opponent can't block with more than one creature."),
|
||||
onlyTwoBlockers ("No more than two creatures can block each combat."),
|
||||
toughnessAssignsDamage ("Each creature assigns combat damage equal to its toughness rather than its power."),
|
||||
blankIsChaos("Each blank roll of the planar dice is a {CHAOS} roll.");
|
||||
|
||||
private final String ruleText;
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -501,7 +501,7 @@ public class AbilityUtils {
|
||||
players.addAll(player.getOpponents());
|
||||
val = playerXCount(players, calcX[1], card, ability);
|
||||
} else if (hType.equals("RegisteredOpponents")) {
|
||||
players.addAll(Iterables.filter(game.getRegisteredPlayers(),PlayerPredicates.isOpponentOf(player)));
|
||||
players.addAll(Iterables.filter(game.getRegisteredPlayers(), PlayerPredicates.isOpponentOf(player)));
|
||||
val = playerXCount(players, calcX[1], card, ability);
|
||||
} else if (hType.equals("Other")) {
|
||||
players.addAll(player.getAllOtherPlayers());
|
||||
@@ -535,6 +535,9 @@ public class AbilityUtils {
|
||||
}
|
||||
}
|
||||
val = playerXCount(players, calcX[1], card, ability);
|
||||
} else if (hType.startsWith("Defined")) {
|
||||
String defined = hType.split("Defined")[1];
|
||||
val = playerXCount(getDefinedPlayers(card, defined, ability), calcX[1], card, ability);
|
||||
} else {
|
||||
val = 0;
|
||||
}
|
||||
@@ -742,6 +745,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 +972,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 +1041,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 +1063,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();
|
||||
}
|
||||
}
|
||||
@@ -1440,7 +1450,7 @@ public class AbilityUtils {
|
||||
|
||||
// The player who has the chance to cancel the ability
|
||||
final String pays = sa.getParamOrDefault("UnlessPayer", "TargetedController");
|
||||
final FCollectionView<Player> allPayers = getDefinedPlayers(sa.getHostCard(), pays, sa);
|
||||
final FCollectionView<Player> allPayers = getDefinedPlayers(source, pays, sa);
|
||||
final String resolveSubs = sa.getParam("UnlessResolveSubs"); // no value means 'Always'
|
||||
final boolean execSubsWhenPaid = "WhenPaid".equals(resolveSubs) || StringUtils.isBlank(resolveSubs);
|
||||
final boolean execSubsWhenNotPaid = "WhenNotPaid".equals(resolveSubs) || StringUtils.isBlank(resolveSubs);
|
||||
@@ -1477,7 +1487,7 @@ public class AbilityUtils {
|
||||
cost = new Cost(new ManaCost(new ManaCostParser(String.valueOf(source.getChosenNumber()))), true);
|
||||
}
|
||||
else if (unlessCost.startsWith("DefinedCost")) {
|
||||
CardCollection definedCards = getDefinedCards(sa.getHostCard(), unlessCost.split("_")[1], sa);
|
||||
CardCollection definedCards = getDefinedCards(source, unlessCost.split("_")[1], sa);
|
||||
if (definedCards.isEmpty()) {
|
||||
sa.resolve();
|
||||
resolveSubAbilities(sa, game);
|
||||
@@ -1497,7 +1507,7 @@ public class AbilityUtils {
|
||||
cost = new Cost(newCost.toManaCost(), true);
|
||||
}
|
||||
else if (unlessCost.startsWith("DefinedSACost")) {
|
||||
FCollection<SpellAbility> definedSAs = getDefinedSpellAbilities(sa.getHostCard(), unlessCost.split("_")[1], sa);
|
||||
FCollection<SpellAbility> definedSAs = getDefinedSpellAbilities(source, unlessCost.split("_")[1], sa);
|
||||
if (definedSAs.isEmpty()) {
|
||||
sa.resolve();
|
||||
resolveSubAbilities(sa, game);
|
||||
@@ -1594,42 +1604,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 +1853,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);
|
||||
}
|
||||
|
||||
@@ -2258,6 +2262,9 @@ public class AbilityUtils {
|
||||
if (sq[0].equals("Monarch")) {
|
||||
return doXMath(calculateAmount(c, sq[player.isMonarch() ? 1 : 2], ctb), expr, c, ctb);
|
||||
}
|
||||
if (sq[0].equals("Initiative")) {
|
||||
return doXMath(calculateAmount(c, sq[player.hasInitiative() ? 1: 2], ctb), expr, c, ctb);
|
||||
}
|
||||
if (sq[0].equals("StartingPlayer")) {
|
||||
return doXMath(calculateAmount(c, sq[player.isStartingPlayer() ? 1: 2], ctb), expr, c, ctb);
|
||||
}
|
||||
@@ -2345,9 +2352,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 +2415,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 +2677,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 +2731,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 +2764,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 +2837,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 +3030,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 +3048,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 +3308,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 +3370,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 +3471,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 +3484,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);
|
||||
}
|
||||
|
||||
@@ -171,6 +171,7 @@ public enum ApiType {
|
||||
Subgame (SubgameEffect.class),
|
||||
Surveil (SurveilEffect.class),
|
||||
SwitchBlock (SwitchBlockEffect.class),
|
||||
TakeInitiative (TakeInitiativeEffect.class),
|
||||
Tap (TapEffect.class),
|
||||
TapAll (TapAllEffect.class),
|
||||
TapOrUntap (TapOrUntapEffect.class),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@ package forge.game.ability.effects;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Lang;
|
||||
|
||||
public class BecomeMonarchEffect extends SpellAbilityEffect {
|
||||
|
||||
@@ -16,8 +15,8 @@ public class BecomeMonarchEffect extends SpellAbilityEffect {
|
||||
|
||||
final List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
|
||||
sb.append(StringUtils.join(tgtPlayers, ", "));
|
||||
sb.append(" becomes the Monarch.");
|
||||
sb.append(Lang.joinHomogenous(tgtPlayers)).append(tgtPlayers.size() == 1 ? " becomes" : " become");
|
||||
sb.append(" the monarch.");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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) : connivers.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,134 @@ 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) {
|
||||
// 111.5. Similarly, if an effect would create a token that is a copy of an instant or sorcery card, no token is created.
|
||||
// instant and sorcery can't enter the battlefield
|
||||
// and it can't be replaced by other tokens
|
||||
if (c.isInstant() || c.isSorcery()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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 +259,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();
|
||||
|
||||
@@ -20,7 +20,7 @@ public class CountersPutAllEffect extends SpellAbilityEffect {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
final CounterType cType = CounterType.getType(sa.getParam("CounterType"));
|
||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CounterNum"), sa);
|
||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParamOrDefault("CounterNum", "1"), sa);
|
||||
final String zone = sa.getParamOrDefault("ValidZone", "Battlefield");
|
||||
|
||||
sb.append("Put ");
|
||||
@@ -45,7 +45,7 @@ public class CountersPutAllEffect extends SpellAbilityEffect {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
final CounterType type = CounterType.getType(sa.getParam("CounterType"));
|
||||
final int counterAmount = AbilityUtils.calculateAmount(host, sa.getParam("CounterNum"), sa);
|
||||
final int counterAmount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("CounterNum", "1"), sa);
|
||||
final String valid = sa.getParam("ValidCards");
|
||||
final ZoneType zone = sa.hasParam("ValidZone") ? ZoneType.smartValueOf(sa.getParam("ValidZone")) : ZoneType.Battlefield;
|
||||
final Game game = activator.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 {
|
||||
|
||||
@@ -33,70 +34,77 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
|
||||
final String toChange = sa.getParamOrDefault("ChangeNum", "1");
|
||||
final int numToChange = toChange.startsWith("All") ? numToDig : AbilityUtils.calculateAmount(host, sa.getParam("ChangeNum"), sa);
|
||||
final List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
|
||||
String verb = " looks at ";
|
||||
if (sa.hasParam("Reveal") && sa.getParam("Reveal").equals("True")) {
|
||||
verb = " reveals ";
|
||||
} else if (sa.hasParam("DestinationZone") && sa.getParam("DestinationZone").equals("Exile") &&
|
||||
numToDig == numToChange) {
|
||||
verb = " exiles ";
|
||||
}
|
||||
sb.append(host.getController()).append(verb).append("the top ");
|
||||
sb.append(numToDig == 1 ? "card" : (Lang.getNumeral(numToDig) + " cards")).append(" of ");
|
||||
|
||||
if (tgtPlayers.contains(host.getController())) {
|
||||
sb.append("their ");
|
||||
final String spellDesc = sa.getParamOrDefault("SpellDescription", "");
|
||||
if (spellDesc.contains("X card")) { // X value can be changed after this goes to the stack, so use set desc
|
||||
sb.append("[").append(host.getController()).append("] ").append(spellDesc);
|
||||
} else {
|
||||
for (final Player p : tgtPlayers) {
|
||||
sb.append(Lang.getInstance().getPossesive(p.getName())).append(" ");
|
||||
final int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
|
||||
final String toChange = sa.getParamOrDefault("ChangeNum", "1");
|
||||
final int numToChange = toChange.startsWith("All") ? numToDig : AbilityUtils.calculateAmount(host, sa.getParam("ChangeNum"), sa);
|
||||
|
||||
String verb = " looks at ";
|
||||
if (sa.hasParam("Reveal") && sa.getParam("Reveal").equals("True")) {
|
||||
verb = " reveals ";
|
||||
} else if (sa.hasParam("DestinationZone") && sa.getParam("DestinationZone").equals("Exile") &&
|
||||
numToDig == numToChange) {
|
||||
verb = " exiles ";
|
||||
}
|
||||
sb.append(host.getController()).append(verb).append("the top ");
|
||||
sb.append(numToDig == 1 ? "card" : Lang.getNumeral(numToDig) + " cards").append(" of ");
|
||||
|
||||
if (tgtPlayers.contains(host.getController())) {
|
||||
sb.append("their ");
|
||||
} else {
|
||||
for (final Player p : tgtPlayers) {
|
||||
sb.append(Lang.getInstance().getPossesive(p.getName())).append(" ");
|
||||
}
|
||||
}
|
||||
sb.append("library.");
|
||||
|
||||
if (numToDig != numToChange) {
|
||||
String destZone1 = sa.hasParam("DestinationZone") ?
|
||||
sa.getParam("DestinationZone").toLowerCase() : "hand";
|
||||
String destZone2 = sa.hasParam("DestinationZone2") ?
|
||||
sa.getParam("DestinationZone2").toLowerCase() : "on the bottom of their library in any order.";
|
||||
if (sa.hasParam("RestRandomOrder")) {
|
||||
destZone2 = destZone2.replace("any", "a random");
|
||||
}
|
||||
|
||||
String verb2 = "put ";
|
||||
String where = " into their hand ";
|
||||
if (destZone1.equals("exile")) {
|
||||
verb2 = "exile ";
|
||||
where = " ";
|
||||
} else if (destZone1.equals("battlefield")) {
|
||||
verb2 = "put ";
|
||||
where = " onto the battlefield ";
|
||||
}
|
||||
|
||||
sb.append(" They ").append(sa.hasParam("Optional") ? "may " : "").append(verb2);
|
||||
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);
|
||||
}
|
||||
sb.append(sa.hasParam("ExileFaceDown") ? "face down " : "");
|
||||
if (sa.hasParam("WithCounter") || sa.hasParam("ExileWithCounter")) {
|
||||
String ctr = sa.hasParam("WithCounter") ? sa.getParam("WithCounter") :
|
||||
sa.getParam("ExileWithCounter");
|
||||
sb.append("with a ");
|
||||
sb.append(CounterType.getType(ctr).getName().toLowerCase());
|
||||
sb.append(" counter on it. They ");
|
||||
} else {
|
||||
sb.append("and ");
|
||||
}
|
||||
sb.append("put the rest ").append(destZone2);
|
||||
}
|
||||
}
|
||||
sb.append("library.");
|
||||
|
||||
if (numToDig != numToChange) {
|
||||
String destZone1 = sa.hasParam("DestinationZone") ?
|
||||
sa.getParam("DestinationZone").toLowerCase() : "hand";
|
||||
String destZone2 = sa.hasParam("DestinationZone2") ?
|
||||
sa.getParam("DestinationZone2").toLowerCase() : "on the bottom of their library in any order.";
|
||||
if (sa.hasParam("RestRandomOrder")) {
|
||||
destZone2 = destZone2.replace("any", "a random");
|
||||
}
|
||||
|
||||
String verb2 = "put ";
|
||||
String where = " in their hand ";
|
||||
if (destZone1.equals("exile")) {
|
||||
verb2 = "exile ";
|
||||
where = " ";
|
||||
} else if (destZone1.equals("battlefield")) {
|
||||
verb2 = "put ";
|
||||
where = " onto the battlefield ";
|
||||
}
|
||||
|
||||
sb.append(" They ").append(sa.hasParam("Optional") ? "may " : "").append(verb2);
|
||||
if (sa.hasParam("ChangeValid")) {
|
||||
String what = sa.hasParam("ChangeValidDesc") ? sa.getParam("ChangeValidDesc") :
|
||||
sa.getParam("ChangeValid");
|
||||
sb.append(Lang.nounWithNumeralExceptOne(numToChange, what)).append(" from among them").append(where);
|
||||
} else {
|
||||
sb.append(Lang.getNumeral(numToChange)).append(" of them").append(where);
|
||||
}
|
||||
sb.append(sa.hasParam("ExileFaceDown") ? "face down " : "");
|
||||
if (sa.hasParam("WithCounter") || sa.hasParam("ExileWithCounter")) {
|
||||
String ctr = sa.hasParam("WithCounter") ? sa.getParam("WithCounter") :
|
||||
sa.getParam("ExileWithCounter");
|
||||
sb.append("with a ");
|
||||
sb.append(CounterType.getType(ctr).getName().toLowerCase());
|
||||
sb.append(" counter on it. They ");
|
||||
} else {
|
||||
sb.append("and ");
|
||||
}
|
||||
sb.append("put the rest ").append(destZone2);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -409,7 +417,9 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
c.setTapped(true);
|
||||
}
|
||||
if (destZone1.equals(ZoneType.Battlefield) && sa.hasParam("WithCounter")) {
|
||||
c.addEtbCounter(CounterType.getType(sa.getParam("WithCounter")), 1, player);
|
||||
final int numCtr = AbilityUtils.calculateAmount(host,
|
||||
sa.getParamOrDefault("WithCounterNum", "1"), sa);
|
||||
c.addEtbCounter(CounterType.getType(sa.getParam("WithCounter")), numCtr, player);
|
||||
}
|
||||
c = game.getAction().moveTo(zone, c, sa, moveParams);
|
||||
if (destZone1.equals(ZoneType.Battlefield)) {
|
||||
@@ -420,6 +430,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 +503,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
|
||||
|
||||
@@ -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,9 +54,8 @@ public class RestartGameEffect extends SpellAbilityEffect {
|
||||
|
||||
game.getStack().reset();
|
||||
game.clearCounterAddedThisTurn();
|
||||
game.resetPlayersAttackedOnNextTurn();
|
||||
game.resetPlayersAttackedOnNextTurn();
|
||||
game.setMonarch(null);
|
||||
game.setHasInitiative(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();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Lang;
|
||||
|
||||
public class TakeInitiativeEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
final List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
|
||||
sb.append(Lang.joinHomogenous(tgtPlayers)).append(tgtPlayers.size() == 1 ? " takes" : " take");
|
||||
sb.append(" the initiative.");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
// TODO: improve ai and fix corner cases
|
||||
final String set = sa.getHostCard().getSetCode();
|
||||
|
||||
for (final Player p : getTargetPlayers(sa)) {
|
||||
if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
|
||||
p.getGame().getAction().takeInitiative(p, set);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -42,12 +42,18 @@ public class VentureEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new dungeon card chosen by player in command zone.
|
||||
List<PaperCard> dungeonCards = StaticData.instance().getVariantCards().getAllCards(
|
||||
Predicates.compose(CardRulesPredicates.Presets.IS_DUNGEON, PaperCard.FN_GET_RULES));
|
||||
|
||||
String message = Localizer.getInstance().getMessage("lblChooseDungeon");
|
||||
Card dungeon = player.getController().chooseDungeon(player, dungeonCards, message);
|
||||
Card dungeon = null;
|
||||
if (sa.hasParam("Dungeon")) {
|
||||
dungeon = Card.fromPaperCard(StaticData.instance().getVariantCards().getUniqueByName(
|
||||
sa.getParam("Dungeon")), player);
|
||||
} else {
|
||||
// Create a new dungeon card chosen by player in command zone.
|
||||
List<PaperCard> dungeonCards = StaticData.instance().getVariantCards().getAllCards(
|
||||
Predicates.compose(CardRulesPredicates.Presets.IS_DUNGEON, PaperCard.FN_GET_RULES));
|
||||
dungeonCards.removeIf(c -> !c.getRules().isEnterableDungeon());
|
||||
String message = Localizer.getInstance().getMessage("lblChooseDungeon");
|
||||
dungeon = player.getController().chooseDungeon(player, dungeonCards, message);
|
||||
}
|
||||
|
||||
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
|
||||
game.getAction().moveTo(ZoneType.Command, dungeon, sa, moveParams);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -54,12 +54,7 @@ import forge.game.replacement.ReplacementHandler;
|
||||
import forge.game.replacement.ReplacementResult;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.*;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityCantAttackBlock;
|
||||
import forge.game.staticability.StaticAbilityCantPutCounter;
|
||||
import forge.game.staticability.StaticAbilityCantSacrifice;
|
||||
import forge.game.staticability.StaticAbilityCantTarget;
|
||||
import forge.game.staticability.StaticAbilityCantTransform;
|
||||
import forge.game.staticability.*;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.trigger.TriggerType;
|
||||
@@ -109,7 +104,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();
|
||||
@@ -195,6 +191,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
|
||||
@@ -337,6 +334,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|
||||
private CombatLki combatLKI;
|
||||
|
||||
private ReplacementEffect shieldCounterReplaceDamage = null;
|
||||
private ReplacementEffect shieldCounterReplaceDestroy = null;
|
||||
|
||||
// Enumeration for CMC request types
|
||||
public enum SplitCMCMode {
|
||||
CurrentSideCMC,
|
||||
@@ -529,7 +529,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);
|
||||
@@ -1079,6 +1079,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);
|
||||
}
|
||||
@@ -1668,6 +1693,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
return;
|
||||
}
|
||||
|
||||
exiledWith.removeExiledCard(this);
|
||||
exiledWith.removeUntilLeavesBattlefield(this);
|
||||
|
||||
exiledWith = null;
|
||||
@@ -1833,6 +1859,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);
|
||||
}
|
||||
@@ -1981,15 +2014,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(":");
|
||||
@@ -2050,7 +2085,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(":");
|
||||
@@ -2542,7 +2577,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")) {
|
||||
@@ -2593,7 +2628,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());
|
||||
@@ -2602,14 +2637,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());
|
||||
@@ -3262,8 +3300,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() {
|
||||
@@ -3405,22 +3449,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);
|
||||
}
|
||||
@@ -3476,11 +3504,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;
|
||||
}
|
||||
|
||||
@@ -4096,8 +4124,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
public final boolean toughnessAssignsDamage() {
|
||||
return getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.toughnessAssignsDamage)
|
||||
|| hasKeyword("CARDNAME assigns combat damage equal to its toughness rather than its power");
|
||||
return StaticAbilityCombatDamageToughness.combatDamageToughness(this);
|
||||
}
|
||||
|
||||
// How much combat damage does the card deal
|
||||
@@ -5159,18 +5186,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());
|
||||
@@ -5193,15 +5212,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;
|
||||
}
|
||||
@@ -6047,17 +6058,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
|
||||
@@ -6069,13 +6077,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() {
|
||||
@@ -6104,6 +6112,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) {
|
||||
@@ -6187,12 +6215,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();
|
||||
@@ -7062,4 +7088,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import com.google.common.collect.Maps;
|
||||
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
@@ -16,25 +18,29 @@ 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;
|
||||
|
||||
boolean hasdealtDamagetoAny = false;
|
||||
private List<GameEntity> attackedThisTurn = Lists.newArrayList();
|
||||
|
||||
private final List<Player> creatureAttackedLastTurnOf = Lists.newArrayList();
|
||||
private final List<Player> NotAttackedSinceLastUpkeepOf = Lists.newArrayList();
|
||||
private final List<Player> NotBlockedSinceLastUpkeepOf = Lists.newArrayList();
|
||||
private final List<Player> NotBeenBlockedSinceLastUpkeepOf = Lists.newArrayList();
|
||||
|
||||
private final Map<GameEntity, Integer> damagedThisCombat = Maps.newHashMap();
|
||||
// only needed for Glen Elendra (Plane)
|
||||
private final List<Player> damagedThisCombat = Lists.newArrayList();
|
||||
// only needed for The Fallen
|
||||
private final FCollection<GameEntity> damagedThisGame = new FCollection<>();
|
||||
|
||||
private final Map<GameEntity, Integer> damagedThisTurn = Maps.newHashMap();
|
||||
private final Map<GameEntity, Integer> damagedThisTurnInCombat = Maps.newHashMap();
|
||||
private final Map<GameEntity, Integer> damagedThisGame = Maps.newHashMap();
|
||||
private boolean receivedNonCombatDamageThisTurn = false;
|
||||
|
||||
public final boolean getHasdealtDamagetoAny() {
|
||||
return !damagedThisGame.isEmpty();
|
||||
return hasdealtDamagetoAny;
|
||||
}
|
||||
|
||||
// used to see if an attacking creature with a triggering attack ability
|
||||
@@ -47,12 +53,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 +70,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 +78,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>
|
||||
@@ -272,7 +248,7 @@ public class CardDamageHistory {
|
||||
this.receivedNonCombatDamageThisTurn = b;
|
||||
}
|
||||
|
||||
public final Map<GameEntity, Integer> getThisCombatDamaged() {
|
||||
public final List<Player> getThisCombatDamaged() {
|
||||
return damagedThisCombat;
|
||||
}
|
||||
public final Map<GameEntity, Integer> getThisTurnDamaged() {
|
||||
@@ -281,7 +257,7 @@ public class CardDamageHistory {
|
||||
public final Map<GameEntity, Integer> getThisTurnCombatDamaged() {
|
||||
return damagedThisTurnInCombat;
|
||||
}
|
||||
public final Map<GameEntity, Integer> getThisGameDamaged() {
|
||||
public final FCollection<GameEntity> getThisGameDamaged() {
|
||||
return damagedThisGame;
|
||||
}
|
||||
/**
|
||||
@@ -290,27 +266,15 @@ public class CardDamageHistory {
|
||||
*/
|
||||
public void registerCombatDamage(GameEntity entity, int amount) {
|
||||
int old = 0;
|
||||
if (damagedThisCombat.containsKey(entity)) {
|
||||
old = damagedThisCombat.get(entity);
|
||||
if (entity instanceof Player) {
|
||||
damagedThisCombat.add((Player) entity);
|
||||
}
|
||||
damagedThisCombat.put(entity, old + amount);
|
||||
old = 0;
|
||||
if (damagedThisTurnInCombat.containsKey(entity)) {
|
||||
old = damagedThisTurnInCombat.get(entity);
|
||||
}
|
||||
damagedThisTurnInCombat.put(entity, old + amount);
|
||||
}
|
||||
/**
|
||||
* TODO: Write javadoc for this method.
|
||||
*/
|
||||
public void newTurn() {
|
||||
damagedThisCombat.clear();
|
||||
damagedThisTurnInCombat.clear();
|
||||
damagedThisTurn.clear();
|
||||
}
|
||||
|
||||
public void endCombat() {
|
||||
damagedThisCombat.clear();
|
||||
hasdealtDamagetoAny = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,11 +287,30 @@ public class CardDamageHistory {
|
||||
old = damagedThisTurn.get(entity);
|
||||
}
|
||||
damagedThisTurn.put(entity, old + amount);
|
||||
old = 0;
|
||||
if (damagedThisGame.containsKey(entity)) {
|
||||
old = damagedThisGame.get(entity);
|
||||
}
|
||||
damagedThisGame.put(entity, old + amount);
|
||||
damagedThisGame.add(entity);
|
||||
hasdealtDamagetoAny = true;
|
||||
}
|
||||
|
||||
public void newTurn() {
|
||||
damagedThisCombat.clear();
|
||||
damagedThisTurnInCombat.clear();
|
||||
damagedThisTurn.clear();
|
||||
attackedThisTurn.clear();
|
||||
|
||||
// if card already LTB we can safely dereference (allows quite a few objects to be cleaned up earlier for bigger boardstates)
|
||||
CardCollection toRemove = new CardCollection();
|
||||
for (GameEntity e : damagedThisGame) {
|
||||
if (e instanceof Card) {
|
||||
if (((Card) e).getZone().getZoneType() != ZoneType.Battlefield) {
|
||||
toRemove.add((Card)e);
|
||||
}
|
||||
}
|
||||
}
|
||||
damagedThisGame.removeAll(toRemove);
|
||||
setHasBeenDealtNonCombatDamageThisTurn(false);
|
||||
}
|
||||
|
||||
public void endCombat() {
|
||||
damagedThisCombat.clear();
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user