mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-14 09:48:02 +00:00
Compare commits
607 Commits
switchedPT
...
paperCardI
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f14454e992 | ||
|
|
e4799a4f73 | ||
|
|
7fa89cbc2e | ||
|
|
567e13c92b | ||
|
|
6f5a933de3 | ||
|
|
e40567c9c8 | ||
|
|
928ac875b5 | ||
|
|
f4831bc51b | ||
|
|
d3e0e79325 | ||
|
|
ac3015eff1 | ||
|
|
554e73e352 | ||
|
|
fe86bb1be3 | ||
|
|
f98eca3925 | ||
|
|
8506fd2bab | ||
|
|
c2ffa227e2 | ||
|
|
f33f780b25 | ||
|
|
855e4dcabd | ||
|
|
40c9a06b21 | ||
|
|
7c4cf9425f | ||
|
|
d2a6329e03 | ||
|
|
6e2717e371 | ||
|
|
2152b7ca7d | ||
|
|
08387f12cb | ||
|
|
01800d3c49 | ||
|
|
2b3735662e | ||
|
|
5ab2b44343 | ||
|
|
0bdb9d7d27 | ||
|
|
1f4cebf186 | ||
|
|
f651a7d73d | ||
|
|
453bae6849 | ||
|
|
292cca0727 | ||
|
|
24f568101f | ||
|
|
ad73651382 | ||
|
|
476f3dfe9c | ||
|
|
5977129096 | ||
|
|
44e243d428 | ||
|
|
491e275b8a | ||
|
|
7bf6ba0779 | ||
|
|
ea33d589ae | ||
|
|
1314c98a5a | ||
|
|
66c1c485f2 | ||
|
|
fe1d37bed6 | ||
|
|
64d54dc7c7 | ||
|
|
050cebf944 | ||
|
|
42e5d9437b | ||
|
|
67aaf9902e | ||
|
|
3d0436aeca | ||
|
|
827d60610d | ||
|
|
69a1d76778 | ||
|
|
7bd5dbbe28 | ||
|
|
39613a8413 | ||
|
|
225ff335e8 | ||
|
|
23a3de5446 | ||
|
|
75fd7bbfda | ||
|
|
ed6a14e180 | ||
|
|
42b6d2c17d | ||
|
|
9a93f0a16c | ||
|
|
1c0d3031d3 | ||
|
|
724697391c | ||
|
|
7820c3f519 | ||
|
|
fe80b3850e | ||
|
|
16a2ae6741 | ||
|
|
ce19c4fb9d | ||
|
|
4911a7f951 | ||
|
|
c6f994d47a | ||
|
|
f0a9077791 | ||
|
|
b6941a9b38 | ||
|
|
7023eb10d3 | ||
|
|
007e132559 | ||
|
|
9b9fdb2e5e | ||
|
|
f1f4310608 | ||
|
|
8634f96e4b | ||
|
|
6eed4c31a2 | ||
|
|
c7b9934072 | ||
|
|
cc50d72d63 | ||
|
|
fbceab3252 | ||
|
|
780491ae4b | ||
|
|
994471e9b6 | ||
|
|
e1bf639e90 | ||
|
|
4386cead3e | ||
|
|
9b0d0f7924 | ||
|
|
b9be1bc647 | ||
|
|
513e4c6c8d | ||
|
|
0f5b67a504 | ||
|
|
72f455067f | ||
|
|
6dc508b631 | ||
|
|
deecf128c3 | ||
|
|
5f497669e5 | ||
|
|
90952d95c9 | ||
|
|
5a5ef52492 | ||
|
|
3b4df7211c | ||
|
|
45329a6df0 | ||
|
|
22607adf57 | ||
|
|
bd9c72fee9 | ||
|
|
35641265dd | ||
|
|
fe37a8358b | ||
|
|
bb7ae64ef8 | ||
|
|
4d9db78cc3 | ||
|
|
ce46c684b5 | ||
|
|
29d4e716f5 | ||
|
|
c6bfdd5b78 | ||
|
|
559daf9fce | ||
|
|
9caa024fa5 | ||
|
|
3605b4e34e | ||
|
|
56832ff987 | ||
|
|
338bb09747 | ||
|
|
b57a5e9ad1 | ||
|
|
67f8f4760e | ||
|
|
485427c682 | ||
|
|
8852732e6b | ||
|
|
6264761117 | ||
|
|
a3b1c98abf | ||
|
|
c566a4bed8 | ||
|
|
a16e2a9c37 | ||
|
|
f80bb13ed7 | ||
|
|
1c350b1766 | ||
|
|
0b5ce9c8fc | ||
|
|
74c7f4b164 | ||
|
|
6128f1d720 | ||
|
|
9e2dcbb630 | ||
|
|
ec9fc88734 | ||
|
|
59c404f6c4 | ||
|
|
fd727a909b | ||
|
|
ce6ad65e12 | ||
|
|
6b65f8972c | ||
|
|
2e7fe8a81b | ||
|
|
6f9db790a6 | ||
|
|
645aff52cb | ||
|
|
4d4afbdf03 | ||
|
|
a8c1f5c969 | ||
|
|
0f4c94d6f8 | ||
|
|
53479c60b4 | ||
|
|
0c7b8c5b04 | ||
|
|
7860957d8f | ||
|
|
07bc31f4c1 | ||
|
|
99390f5967 | ||
|
|
ee8ca02128 | ||
|
|
1bc7efba65 | ||
|
|
efc2357905 | ||
|
|
d5d01011f4 | ||
|
|
c1a19ea4ae | ||
|
|
ad3fb1137a | ||
|
|
c5c6b36f4f | ||
|
|
f2f92212bc | ||
|
|
3226be58be | ||
|
|
46cfcff0ca | ||
|
|
b161c9612b | ||
|
|
91dee5c379 | ||
|
|
349129f88f | ||
|
|
57ea25bbbd | ||
|
|
d1725af64d | ||
|
|
72626ef214 | ||
|
|
e00d5ee30b | ||
|
|
d6c42c3c8c | ||
|
|
950a1f2a44 | ||
|
|
c895b0eeab | ||
|
|
82c8fb20e8 | ||
|
|
cf54f3e04e | ||
|
|
f4958a4b49 | ||
|
|
760f9412ff | ||
|
|
71f2d41eb0 | ||
|
|
51d7933ef3 | ||
|
|
f5938b47e1 | ||
|
|
807d078799 | ||
|
|
bedb97183b | ||
|
|
08919e3375 | ||
|
|
a117d65f51 | ||
|
|
b61e3015f3 | ||
|
|
83a512a075 | ||
|
|
c1630a2e47 | ||
|
|
1d0b50356f | ||
|
|
43d82ce1ce | ||
|
|
c4a8765d6c | ||
|
|
15d49bc1b1 | ||
|
|
d84712b65d | ||
|
|
eac94f7249 | ||
|
|
d134c3dfcd | ||
|
|
0a03327299 | ||
|
|
0a673aeadc | ||
|
|
40f7a9a22a | ||
|
|
e2ccf8960a | ||
|
|
66acc6b920 | ||
|
|
83abcf7c44 | ||
|
|
ef9d807b6e | ||
|
|
83f334888e | ||
|
|
8359cfda35 | ||
|
|
93b2d7c795 | ||
|
|
ea74cc8f7f | ||
|
|
4e16a5ea60 | ||
|
|
00325fb511 | ||
|
|
fe042fa6d9 | ||
|
|
6830f15859 | ||
|
|
fb3a8ce003 | ||
|
|
edc6edc44b | ||
|
|
40b3edd426 | ||
|
|
f50aea1576 | ||
|
|
4ab67b1d3b | ||
|
|
a4e4525769 | ||
|
|
1d664145e0 | ||
|
|
32355499aa | ||
|
|
a475855c4c | ||
|
|
598d3a4958 | ||
|
|
35a4053212 | ||
|
|
9d770a9bca | ||
|
|
de15ab3a71 | ||
|
|
35bc80767f | ||
|
|
ef28a92dff | ||
|
|
99f3ccb8d6 | ||
|
|
16148ec992 | ||
|
|
93f7987312 | ||
|
|
c7816daaf6 | ||
|
|
bc458ecde4 | ||
|
|
e4da62c254 | ||
|
|
53588dfc24 | ||
|
|
61c11b38a0 | ||
|
|
fdcc33198b | ||
|
|
dd7a0e99e2 | ||
|
|
cb0e594a6e | ||
|
|
059881a7b5 | ||
|
|
ca59ac925c | ||
|
|
0b5f14cd9c | ||
|
|
1d987c25e9 | ||
|
|
5cb68bc2f0 | ||
|
|
1715efced7 | ||
|
|
aeb39b99bb | ||
|
|
778066a622 | ||
|
|
75a056abe6 | ||
|
|
3915f316e2 | ||
|
|
b52646ee7e | ||
|
|
cb1d48a566 | ||
|
|
733d56246e | ||
|
|
7902ad71d7 | ||
|
|
2666de0adb | ||
|
|
c2613a03b0 | ||
|
|
b077cca3ac | ||
|
|
43fc281e65 | ||
|
|
7b1f5d0a34 | ||
|
|
330fe0a0a8 | ||
|
|
30a4c24c35 | ||
|
|
01a6a38285 | ||
|
|
fe5aa37791 | ||
|
|
42f1fba7c4 | ||
|
|
0c6f1ff58f | ||
|
|
8678b5ec5b | ||
|
|
71256972c4 | ||
|
|
a76f23fc25 | ||
|
|
3c7993d640 | ||
|
|
71d984a5ff | ||
|
|
be171c011a | ||
|
|
becdd180f4 | ||
|
|
41efdb095a | ||
|
|
86453a6fa7 | ||
|
|
8448a6e20e | ||
|
|
194662c9c7 | ||
|
|
d3a33a0092 | ||
|
|
2bab13247c | ||
|
|
da30b886c9 | ||
|
|
da38641ff8 | ||
|
|
fcbc83c3ff | ||
|
|
4ae93980b6 | ||
|
|
10b96138c3 | ||
|
|
8706edbb01 | ||
|
|
4f5c074fe6 | ||
|
|
50ca71fc87 | ||
|
|
84134341e3 | ||
|
|
988518284c | ||
|
|
1476621df1 | ||
|
|
cb8e13e933 | ||
|
|
ea4d878a2d | ||
|
|
eeafc6400c | ||
|
|
875b4557b0 | ||
|
|
bec25c68f2 | ||
|
|
f98aae35a5 | ||
|
|
b1acb9f0a3 | ||
|
|
89049db6a0 | ||
|
|
77378855e3 | ||
|
|
709582086f | ||
|
|
7eda9e204e | ||
|
|
cf94d96d95 | ||
|
|
a78d361b2e | ||
|
|
d01619c09c | ||
|
|
fd76098765 | ||
|
|
9fb08f1695 | ||
|
|
4eef86daf1 | ||
|
|
d74d2da755 | ||
|
|
f69fd12ebd | ||
|
|
d41cdca1bd | ||
|
|
acfc413465 | ||
|
|
7a2ef6c9fd | ||
|
|
b214b6f8bf | ||
|
|
4d75f7b225 | ||
|
|
b7db4b4cbc | ||
|
|
efaca50d97 | ||
|
|
0157113103 | ||
|
|
bf70d8e6f6 | ||
|
|
30ac5ae381 | ||
|
|
dd94169c9a | ||
|
|
4c24e7248d | ||
|
|
40eb8de90c | ||
|
|
8f60973d95 | ||
|
|
bc9df8872f | ||
|
|
1071089879 | ||
|
|
28ae7fa729 | ||
|
|
e9c5bc46ae | ||
|
|
9308dac257 | ||
|
|
f5fa87410b | ||
|
|
c3167a9928 | ||
|
|
c533bee4b3 | ||
|
|
01a3b2723f | ||
|
|
8640a2aaad | ||
|
|
9f5e5f3068 | ||
|
|
3db209f128 | ||
|
|
351a94281c | ||
|
|
f190e7e678 | ||
|
|
e01d3e0a6d | ||
|
|
cb3fcb259f | ||
|
|
0940e7433b | ||
|
|
8e48ff3dfa | ||
|
|
07b10f736b | ||
|
|
d0e4e5bd17 | ||
|
|
fd082ffabb | ||
|
|
520dcb88d6 | ||
|
|
a4138ebd4d | ||
|
|
1118f5b135 | ||
|
|
e78f921e4d | ||
|
|
a6ec235052 | ||
|
|
613cc549f7 | ||
|
|
69c7083592 | ||
|
|
7f64e25691 | ||
|
|
2cb107380d | ||
|
|
2a4eaea90a | ||
|
|
8fdbaf3611 | ||
|
|
55edec665f | ||
|
|
f11c6a7038 | ||
|
|
36150be7f3 | ||
|
|
b861994d2f | ||
|
|
0242189012 | ||
|
|
69ddfdc18c | ||
|
|
bef56d1caa | ||
|
|
c7f43e245d | ||
|
|
939b7a22e0 | ||
|
|
a79a3503f5 | ||
|
|
9109207495 | ||
|
|
dbac513027 | ||
|
|
3152ac93f6 | ||
|
|
fc56076f81 | ||
|
|
ecc763faf6 | ||
|
|
0130367873 | ||
|
|
c36b9b7610 | ||
|
|
774ab38335 | ||
|
|
251ce2f83b | ||
|
|
2af47fe6df | ||
|
|
332c982695 | ||
|
|
eb970665a6 | ||
|
|
4714319204 | ||
|
|
c85214b9e3 | ||
|
|
d5854c9d1c | ||
|
|
a3a2a5dd1b | ||
|
|
8f155c9cca | ||
|
|
366ed643a7 | ||
|
|
e7becacd57 | ||
|
|
2cf9293ab3 | ||
|
|
88300de6e5 | ||
|
|
a4cb0924f3 | ||
|
|
e0001f8348 | ||
|
|
e380590c4c | ||
|
|
7410a2844f | ||
|
|
b4af62eda7 | ||
|
|
9c14ba49a3 | ||
|
|
25900ee10c | ||
|
|
443ba2c1e0 | ||
|
|
6ade60cd59 | ||
|
|
436697e0b3 | ||
|
|
e7d8664386 | ||
|
|
e2e3c658a0 | ||
|
|
2a4ea4cb5d | ||
|
|
93215e6ce4 | ||
|
|
f4a0de6392 | ||
|
|
579033abb3 | ||
|
|
8328866645 | ||
|
|
890ce2505c | ||
|
|
7e6697d100 | ||
|
|
84d793b786 | ||
|
|
386550da39 | ||
|
|
6d3d11398d | ||
|
|
5db9c189ba | ||
|
|
8d3e3cb253 | ||
|
|
2042aaa033 | ||
|
|
6fc5d218c9 | ||
|
|
6332f5cbfc | ||
|
|
44340998fd | ||
|
|
5274c976ef | ||
|
|
280d2fed6d | ||
|
|
9ecea646d2 | ||
|
|
c4e5101a42 | ||
|
|
48a555817d | ||
|
|
2cc2ae421a | ||
|
|
9109b26484 | ||
|
|
98e5eb9652 | ||
|
|
4e93f95dff | ||
|
|
44e1332fb4 | ||
|
|
e2972acad0 | ||
|
|
76086ffd35 | ||
|
|
3c3c47616b | ||
|
|
de5a660a6a | ||
|
|
d348a72ae4 | ||
|
|
4055512698 | ||
|
|
b4b2346fb8 | ||
|
|
caa5443874 | ||
|
|
c7fb0bd3c3 | ||
|
|
1f5f62b21a | ||
|
|
04937b6447 | ||
|
|
94ea88b171 | ||
|
|
2188582e16 | ||
|
|
fbc73fa22b | ||
|
|
31790c3dc9 | ||
|
|
cdf3038ab6 | ||
|
|
ff1781b734 | ||
|
|
49d7351eac | ||
|
|
3b372eead7 | ||
|
|
c815f7ed0c | ||
|
|
04ac8d8de9 | ||
|
|
6b961ed3e1 | ||
|
|
17a2a23981 | ||
|
|
74e83f2a94 | ||
|
|
8e25dd0e25 | ||
|
|
7f9260f54c | ||
|
|
505d4b4f9a | ||
|
|
baca196066 | ||
|
|
b08cf3bf5b | ||
|
|
b5ee3eb43c | ||
|
|
081e1a2960 | ||
|
|
d0310e257b | ||
|
|
a3733f1fa8 | ||
|
|
b4bd7947f9 | ||
|
|
b56da433eb | ||
|
|
64b5906f08 | ||
|
|
f7d94fabc9 | ||
|
|
87e0810603 | ||
|
|
93f2fa8f43 | ||
|
|
c86bf402fd | ||
|
|
9926004cf1 | ||
|
|
d0c24f49a9 | ||
|
|
661f3b8e7a | ||
|
|
14249150a0 | ||
|
|
93dccdeace | ||
|
|
706ef4ac6c | ||
|
|
bd3994a217 | ||
|
|
e722c4b63c | ||
|
|
a062e0040d | ||
|
|
7fdd645026 | ||
|
|
3670891ec9 | ||
|
|
6f3dd8deba | ||
|
|
63ac4a3ee4 | ||
|
|
4616ee715e | ||
|
|
5ef6bf1c15 | ||
|
|
862b4e19b6 | ||
|
|
378524dc39 | ||
|
|
6d5f45a311 | ||
|
|
0e00a52eb4 | ||
|
|
4b69d16c6d | ||
|
|
92e17a66f2 | ||
|
|
b601431591 | ||
|
|
76db40189e | ||
|
|
8ddf8225c0 | ||
|
|
359dd8d641 | ||
|
|
878da9b06f | ||
|
|
d843004ad6 | ||
|
|
7f6024f81f | ||
|
|
5a37b49fcd | ||
|
|
1c8cdac5be | ||
|
|
20bd27d487 | ||
|
|
fc761220d2 | ||
|
|
144681012c | ||
|
|
8fe3bd3c79 | ||
|
|
da65308cf2 | ||
|
|
011457e949 | ||
|
|
c296025837 | ||
|
|
ac4c501629 | ||
|
|
fe062a9312 | ||
|
|
65d4505b67 | ||
|
|
073d7e537c | ||
|
|
77dc367c95 | ||
|
|
7553d164f4 | ||
|
|
ee01e3d29f | ||
|
|
58f8c39197 | ||
|
|
7304fa862a | ||
|
|
1ef8b9ca47 | ||
|
|
5191d2f9c4 | ||
|
|
ff74e36fe5 | ||
|
|
0121619e93 | ||
|
|
aeb279a6f8 | ||
|
|
20815552b9 | ||
|
|
ac67a36ccf | ||
|
|
fcd8b8fd35 | ||
|
|
fbe4ad5c44 | ||
|
|
ee17483fff | ||
|
|
a451f1a234 | ||
|
|
52c4c01a7d | ||
|
|
b1bb0d669f | ||
|
|
eb1f9783aa | ||
|
|
0724d224fa | ||
|
|
e7775cdfa9 | ||
|
|
f8836f0c40 | ||
|
|
4c342cfc6a | ||
|
|
df05ab34fb | ||
|
|
5da0e75252 | ||
|
|
92ec5d8f64 | ||
|
|
6515fed9d2 | ||
|
|
5a7cd40614 | ||
|
|
65b01e0822 | ||
|
|
643f893d43 | ||
|
|
e0c6b43214 | ||
|
|
0f5d71f933 | ||
|
|
e6a8b5ed74 | ||
|
|
24c11e47c4 | ||
|
|
cb5f805767 | ||
|
|
3788e01f38 | ||
|
|
a9df4ea424 | ||
|
|
25ba06d530 | ||
|
|
9b81644f11 | ||
|
|
4b27536ed3 | ||
|
|
148da24456 | ||
|
|
d1be43fd83 | ||
|
|
f2df505237 | ||
|
|
bd37e26fab | ||
|
|
7954473476 | ||
|
|
13287cefbd | ||
|
|
9afbc91de1 | ||
|
|
dfe5bd9ec9 | ||
|
|
e2411e34bd | ||
|
|
f2998bdf9a | ||
|
|
c52f886e89 | ||
|
|
f972aa44ba | ||
|
|
ccafe0557f | ||
|
|
f9f9b1a1f9 | ||
|
|
e867aacbf5 | ||
|
|
a09e9e4fd6 | ||
|
|
fc320e6524 | ||
|
|
2b6a1c9f3d | ||
|
|
3e9cd2c226 | ||
|
|
3f722abba2 | ||
|
|
06508f70a3 | ||
|
|
8580108d1e | ||
|
|
300f34377c | ||
|
|
c4828f510f | ||
|
|
04c400553a | ||
|
|
d2508333bc | ||
|
|
fc901f1ebb | ||
|
|
c365f5a3d1 | ||
|
|
49697c863c | ||
|
|
4431c40de6 | ||
|
|
a0f6efb959 | ||
|
|
44fea0ae75 | ||
|
|
95c970e23f | ||
|
|
8596151fa1 | ||
|
|
2eac43734c | ||
|
|
dff91eb2aa | ||
|
|
aa122700a9 | ||
|
|
80f267df59 | ||
|
|
235618c3bb | ||
|
|
b7e55e785e | ||
|
|
050c986d08 | ||
|
|
2b83541ebc | ||
|
|
d40894ef6a | ||
|
|
b756bda988 | ||
|
|
0e64a88005 | ||
|
|
0d952a54bd | ||
|
|
d3ff7f3b61 | ||
|
|
0f9e7eca89 | ||
|
|
207b786fcd | ||
|
|
0f6fa87da0 | ||
|
|
995c1167dc | ||
|
|
1ab1d9c002 | ||
|
|
4b1a6a2f87 | ||
|
|
dacecd9006 | ||
|
|
dd5d75613e | ||
|
|
d59a316d8c | ||
|
|
9c4f855f71 | ||
|
|
90131b4a70 | ||
|
|
fb6725f2d7 | ||
|
|
475c57af55 | ||
|
|
6ae119a415 | ||
|
|
8b5fe276e7 | ||
|
|
5e4d5c262d | ||
|
|
9b82f1ef1f | ||
|
|
321d2d7e33 | ||
|
|
5b7cca95e1 | ||
|
|
2e0d53c6fe | ||
|
|
9d4f6d2cbb | ||
|
|
9546b434e4 | ||
|
|
f230522657 | ||
|
|
a57a1f566a | ||
|
|
8f8d6e6e30 | ||
|
|
3b8694483c | ||
|
|
4328a12967 | ||
|
|
fdc85b85c3 | ||
|
|
bb2eed23b7 | ||
|
|
72e33146de | ||
|
|
e371617938 | ||
|
|
b363db2bbd | ||
|
|
37a5958750 | ||
|
|
9091cfe3b0 | ||
|
|
e2614187ac | ||
|
|
5c69bf0470 | ||
|
|
383dc85166 | ||
|
|
7e47208888 |
101
.github/workflows/maven-publish.yml
vendored
101
.github/workflows/maven-publish.yml
vendored
@@ -2,10 +2,21 @@ name: Publish Desktop Forge
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
debug_enabled:
|
||||||
|
type: boolean
|
||||||
|
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
release_android:
|
||||||
|
type: boolean
|
||||||
|
description: 'Also try to release android build'
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
if: github.repository_owner == 'Card-Forge'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -32,10 +43,94 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
git config user.email "actions@github.com"
|
git config user.email "actions@github.com"
|
||||||
git config user.name "GitHub Actions"
|
git config user.name "GitHub Actions"
|
||||||
- name: Build/Install/Publish to GitHub Packages Apache Maven
|
|
||||||
|
- name: Install old maven (3.8.1)
|
||||||
|
run: |
|
||||||
|
curl -o apache-maven-3.8.1-bin.tar.gz https://archive.apache.org/dist/maven/maven-3/3.8.1/binaries/apache-maven-3.8.1-bin.tar.gz
|
||||||
|
tar xf apache-maven-3.8.1-bin.tar.gz
|
||||||
|
export PATH=$PWD/apache-maven-3.8.1/bin:$PATH
|
||||||
|
export MAVEN_HOME=$PWD/apache-maven-3.8.1
|
||||||
|
mvn --version
|
||||||
|
|
||||||
|
- name: Setup tmate session
|
||||||
|
uses: mxschmitt/action-tmate@v3
|
||||||
|
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
|
||||||
|
|
||||||
|
- name: Setup android requirements
|
||||||
|
if: ${{ github.event_name == 'workflow_dispatch' && inputs.release_android }}
|
||||||
|
run: |
|
||||||
|
JAVA_HOME=${JAVA_HOME_17_X64} ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --sdk_root=$ANDROID_SDK_ROOT --install "build-tools;35.0.0" "platform-tools" "platforms;android-35"
|
||||||
|
cd forge-gui-android
|
||||||
|
echo "${{ secrets.FORGE_KEYSTORE }}" > forge.keystore.asc
|
||||||
|
gpg -d --passphrase "${{ secrets.FORGE_KEYSTORE_PASSPHRASE }}" --batch forge.keystore.asc > forge.keystore
|
||||||
|
cd -
|
||||||
|
mkdir -p ~/.m2/repository/com/simpligility/maven/plugins/android-maven-plugin/4.6.2
|
||||||
|
cd ~/.m2/repository/com/simpligility/maven/plugins/android-maven-plugin/4.6.2
|
||||||
|
curl -L -o android-maven-plugin-4.6.2.jar https://github.com/Card-Forge/android-maven-plugin/releases/download/4.6.2/android-maven-plugin-4.6.2.jar
|
||||||
|
curl -L -o android-maven-plugin-4.6.2.pom https://github.com/Card-Forge/android-maven-plugin/releases/download/4.6.2/android-maven-plugin-4.6.2.pom
|
||||||
|
cd -
|
||||||
|
mvn install -Dmaven.test.skip=true
|
||||||
|
mvn dependency:tree
|
||||||
|
|
||||||
|
|
||||||
|
- name: Build/Install/Publish Desktop to GitHub Packages Apache Maven
|
||||||
|
if: ${{ github.event_name == 'workflow_dispatch' && !inputs.release_android }}
|
||||||
run: |
|
run: |
|
||||||
export DISPLAY=":1"
|
export DISPLAY=":1"
|
||||||
Xvfb :1 -screen 0 800x600x8 &
|
Xvfb :1 -screen 0 800x600x8 &
|
||||||
mvn -U -B clean -P windows-linux install release:clean release:prepare release:perform -T 1C -Dcardforge-repo.username=${{ secrets.FTP_USERNAME }} -Dcardforge-repo.password=${{ secrets.FTP_PASSWORD }}
|
export _JAVA_OPTIONS="-Xmx2g"
|
||||||
|
d=$(date +%m.%d)
|
||||||
|
# build only desktop and only try to move desktop files
|
||||||
|
mvn -U -B clean -P windows-linux install -e -T 1C release:clean release:prepare release:perform -DskipTests
|
||||||
|
mkdir izpack
|
||||||
|
# move bz2 and jar from work dir to izpack dir
|
||||||
|
mv /home/runner/work/forge/forge/forge-installer/*/*.{bz2,jar} izpack/
|
||||||
|
# move desktop build.txt and version.txt to izpack
|
||||||
|
mv /home/runner/work/forge/forge/forge-gui-desktop/target/classes/*.txt izpack/
|
||||||
|
cd izpack
|
||||||
|
ls
|
||||||
|
echo "GIT_TAG=`echo $(git describe --tags --abbrev=0)`" >> $GITHUB_ENV
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
|
||||||
|
- name: Build/Install/Publish Desktop+Android to GitHub Packages Apache Maven
|
||||||
|
if: ${{ github.event_name == 'workflow_dispatch' && inputs.release_android }}
|
||||||
|
run: |
|
||||||
|
export DISPLAY=":1"
|
||||||
|
Xvfb :1 -screen 0 800x600x8 &
|
||||||
|
export _JAVA_OPTIONS="-Xmx2g"
|
||||||
|
d=$(date +%m.%d)
|
||||||
|
# build both desktop and android
|
||||||
|
mvn -U -B clean -P windows-linux,android-release-build install -e -Dcardforge-repo.username=${{ secrets.FTP_USERNAME }} -Dcardforge-repo.password=${{ secrets.FTP_PASSWORD }} -Dandroid.sdk.path=/usr/local/lib/android/sdk -Dandroid.buildToolsVersion=35.0.0
|
||||||
|
mkdir izpack
|
||||||
|
# move bz2 and jar from work dir to izpack dir
|
||||||
|
mv /home/runner/work/forge/forge/forge-installer/*/*.{bz2,jar} izpack/
|
||||||
|
# move desktop build.txt and version.txt to izpack
|
||||||
|
mv /home/runner/work/forge/forge/forge-gui-desktop/target/classes/*.txt izpack/
|
||||||
|
# move android apk and assets.zip
|
||||||
|
mv /home/runner/work/forge/forge/forge-gui-android/target/*-signed-aligned.apk izpack/
|
||||||
|
mv /home/runner/work/forge/forge/forge-gui-android/target/assets.zip izpack/
|
||||||
|
cd izpack
|
||||||
|
ls
|
||||||
|
echo "GIT_TAG=`echo $(git describe --tags --abbrev=0)`" >> $GITHUB_ENV
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
|
||||||
|
- name: Upload snapshot to GitHub Prerelease
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
name: Release ${{ env.GIT_TAG }}
|
||||||
|
tag: ${{ env.GIT_TAG }}
|
||||||
|
artifacts: izpack/*
|
||||||
|
allowUpdates: true
|
||||||
|
removeArtifacts: true
|
||||||
|
makeLatest: true
|
||||||
|
|
||||||
|
- name: Send failure notification to Discord
|
||||||
|
if: failure() # This step runs only if the job fails
|
||||||
|
run: |
|
||||||
|
curl -X POST -H "Content-Type: application/json" \
|
||||||
|
-d "{\"content\": \"🔴 Release Build Failed in branch: \`${{ github.ref_name }}\` by \`${{ github.actor }}\`.\nCheck logs: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}" \
|
||||||
|
${{ secrets.DISCORD_AUTOMATION_WEBHOOK }}
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ Join the **Forge community** on [Discord](https://discord.gg/HcPJNyD66a)!
|
|||||||
|
|
||||||
### 📥 Desktop Installation
|
### 📥 Desktop Installation
|
||||||
1. **Latest Releases:** Download the latest version [here](https://github.com/Card-Forge/forge/releases/latest).
|
1. **Latest Releases:** Download the latest version [here](https://github.com/Card-Forge/forge/releases/latest).
|
||||||
2. **Snapshot Build:** For the latest development version, grab the `forge-gui-desktop` tarball from our [Snapshot Build](https://downloads.cardforge.org/dailysnapshots/).
|
2. **Snapshot Build:** For the latest development version, grab the `forge-gui-desktop` tarball from our [Snapshot Build](https://github.com/Card-Forge/forge/releases/tag/daily-snapshots).
|
||||||
- **Tip:** Extract to a new folder to prevent version conflicts.
|
- **Tip:** Extract to a new folder to prevent version conflicts.
|
||||||
3. **User Data Management:** Previous players’ data is preserved during upgrades.
|
3. **User Data Management:** Previous players’ data is preserved during upgrades.
|
||||||
4. **Java Requirement:** Ensure you have **Java 17 or later** installed.
|
4. **Java Requirement:** Ensure you have **Java 17 or later** installed.
|
||||||
|
|
||||||
### 📱 Android Installation
|
### 📱 Android Installation
|
||||||
- Download the **APK** from the [Snapshot Build](https://downloads.cardforge.org/dailysnapshots/). On the first launch, Forge will automatically download all necessary assets.
|
- Download the **APK** from the [Snapshot Build](https://github.com/Card-Forge/forge/releases/tag/daily-snapshots). On the first launch, Forge will automatically download all necessary assets.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import forge.game.spellability.SpellAbility;
|
|||||||
import forge.game.spellability.SpellAbilityPredicates;
|
import forge.game.spellability.SpellAbilityPredicates;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
|
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
|
||||||
|
import forge.game.staticability.StaticAbilityMode;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
@@ -1587,7 +1588,7 @@ public class AiAttackController {
|
|||||||
// but there are no creatures it can target, no need to exert with it
|
// but there are no creatures it can target, no need to exert with it
|
||||||
boolean missTarget = false;
|
boolean missTarget = false;
|
||||||
for (StaticAbility st : c.getStaticAbilities()) {
|
for (StaticAbility st : c.getStaticAbilities()) {
|
||||||
if (!"OptionalAttackCost".equals(st.getParam("Mode"))) {
|
if (!st.checkMode(StaticAbilityMode.OptionalAttackCost)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
SpellAbility sa = st.getPayingTrigSA();
|
SpellAbility sa = st.getPayingTrigSA();
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import forge.game.replacement.ReplacementType;
|
|||||||
import forge.game.spellability.*;
|
import forge.game.spellability.*;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.staticability.StaticAbilityDisableTriggers;
|
import forge.game.staticability.StaticAbilityDisableTriggers;
|
||||||
|
import forge.game.staticability.StaticAbilityMode;
|
||||||
import forge.game.staticability.StaticAbilityMustTarget;
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
@@ -68,8 +69,10 @@ import java.util.*;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
@@ -1127,7 +1130,7 @@ public class AiController {
|
|||||||
// Memory Crystal-like effects need special handling
|
// Memory Crystal-like effects need special handling
|
||||||
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
for (StaticAbility s : c.getStaticAbilities()) {
|
for (StaticAbility s : c.getStaticAbilities()) {
|
||||||
if ("ReduceCost".equals(s.getParam("Mode"))
|
if (s.checkMode(StaticAbilityMode.ReduceCost)
|
||||||
&& "Spell.Buyback".equals(s.getParam("ValidSpell"))) {
|
&& "Spell.Buyback".equals(s.getParam("ValidSpell"))) {
|
||||||
neededMana -= AbilityUtils.calculateAmount(c, s.getParam("Amount"), s);
|
neededMana -= AbilityUtils.calculateAmount(c, s.getParam("Amount"), s);
|
||||||
}
|
}
|
||||||
@@ -1707,7 +1710,8 @@ public class AiController {
|
|||||||
Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
|
Sentry.captureMessage(ex.getMessage() + "\nAssertionError [verifyTransitivity]: " + assertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
CompletableFuture<SpellAbility> future = CompletableFuture.supplyAsync(() -> {
|
final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
Future<SpellAbility> future = executor.submit(() -> {
|
||||||
//avoid ComputerUtil.aiLifeInDanger in loops as it slows down a lot.. call this outside loops will generally be fast...
|
//avoid ComputerUtil.aiLifeInDanger in loops as it slows down a lot.. call this outside loops will generally be fast...
|
||||||
boolean isLifeInDanger = useLivingEnd && ComputerUtil.aiLifeInDanger(player, true, 0);
|
boolean isLifeInDanger = useLivingEnd && ComputerUtil.aiLifeInDanger(player, true, 0);
|
||||||
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
|
for (final SpellAbility sa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player)) {
|
||||||
@@ -1787,11 +1791,9 @@ public class AiController {
|
|||||||
|
|
||||||
// instead of computing all available concurrently just add a simple timeout depending on the user prefs
|
// instead of computing all available concurrently just add a simple timeout depending on the user prefs
|
||||||
try {
|
try {
|
||||||
if (game.AI_CAN_USE_TIMEOUT)
|
return future.get(game.getAITimeout(), TimeUnit.SECONDS);
|
||||||
return future.completeOnTimeout(null, game.getAITimeout(), TimeUnit.SECONDS).get();
|
|
||||||
else
|
|
||||||
return future.get(game.getAITimeout(), TimeUnit.SECONDS);
|
|
||||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||||
|
future.cancel(true);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2367,7 +2369,7 @@ public class AiController {
|
|||||||
|
|
||||||
// TODO move to more common place
|
// TODO move to more common place
|
||||||
public static <T extends TriggerReplacementBase> List<T> filterList(List<T> input, Function<SpellAbility, Object> pred, Object value) {
|
public static <T extends TriggerReplacementBase> List<T> filterList(List<T> input, Function<SpellAbility, Object> pred, Object value) {
|
||||||
return filterList(input, trb -> pred.apply(trb.ensureAbility()) == value);
|
return filterList(input, trb -> trb.ensureAbility() != null && pred.apply(trb.ensureAbility()) == value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<SpellAbility> filterListByApi(List<SpellAbility> input, ApiType type) {
|
public static List<SpellAbility> filterListByApi(List<SpellAbility> input, ApiType type) {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import forge.game.spellability.SpellAbility;
|
|||||||
import forge.game.spellability.SpellAbilityStackInstance;
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
|
import forge.game.staticability.StaticAbilityMode;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.trigger.WrappedAbility;
|
import forge.game.trigger.WrappedAbility;
|
||||||
@@ -1458,15 +1459,14 @@ public class ComputerUtil {
|
|||||||
// check for Continuous abilities that grant Haste
|
// check for Continuous abilities that grant Haste
|
||||||
for (final Card c : all) {
|
for (final Card c : all) {
|
||||||
for (StaticAbility stAb : c.getStaticAbilities()) {
|
for (StaticAbility stAb : c.getStaticAbilities()) {
|
||||||
Map<String, String> params = stAb.getMapParams();
|
if (stAb.checkMode(StaticAbilityMode.Continuous) && stAb.hasParam("AddKeyword")
|
||||||
if ("Continuous".equals(params.get("Mode")) && params.containsKey("AddKeyword")
|
&& stAb.getParam("AddKeyword").contains("Haste")) {
|
||||||
&& params.get("AddKeyword").contains("Haste")) {
|
|
||||||
|
|
||||||
if (c.isEquipment() && c.getEquipping() == null) {
|
if (c.isEquipment() && c.getEquipping() == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String affected = params.get("Affected");
|
final String affected = stAb.getParam("Affected");
|
||||||
if (affected.contains("Creature.YouCtrl")
|
if (affected.contains("Creature.YouCtrl")
|
||||||
|| affected.contains("Other+YouCtrl")) {
|
|| affected.contains("Other+YouCtrl")) {
|
||||||
return true;
|
return true;
|
||||||
@@ -1519,11 +1519,10 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
for (final Card c : opp) {
|
for (final Card c : opp) {
|
||||||
for (StaticAbility stAb : c.getStaticAbilities()) {
|
for (StaticAbility stAb : c.getStaticAbilities()) {
|
||||||
Map<String, String> params = stAb.getMapParams();
|
if (stAb.checkMode(StaticAbilityMode.Continuous) && stAb.hasParam("AddKeyword")
|
||||||
if ("Continuous".equals(params.get("Mode")) && params.containsKey("AddKeyword")
|
&& stAb.getParam("AddKeyword").contains("Haste")) {
|
||||||
&& params.get("AddKeyword").contains("Haste")) {
|
|
||||||
|
|
||||||
final ArrayList<String> affected = Lists.newArrayList(params.get("Affected").split(","));
|
final ArrayList<String> affected = Lists.newArrayList(stAb.getParam("Affected").split(","));
|
||||||
if (affected.contains("Creature")) {
|
if (affected.contains("Creature")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2429,7 +2428,7 @@ public class ComputerUtil {
|
|||||||
// Are we picking a type to reduce costs for that type?
|
// Are we picking a type to reduce costs for that type?
|
||||||
boolean reducingCost = false;
|
boolean reducingCost = false;
|
||||||
for (StaticAbility s : sa.getHostCard().getStaticAbilities()) {
|
for (StaticAbility s : sa.getHostCard().getStaticAbilities()) {
|
||||||
if ("ReduceCost".equals(s.getParam("Mode")) && "Card.ChosenType".equals(s.getParam("ValidCard"))) {
|
if (s.checkMode(StaticAbilityMode.ReduceCost) && "Card.ChosenType".equals(s.getParam("ValidCard"))) {
|
||||||
reducingCost = true;
|
reducingCost = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2891,7 +2890,7 @@ public class ComputerUtil {
|
|||||||
// Iceberg does use Ice as Storage
|
// Iceberg does use Ice as Storage
|
||||||
|| (type.is(CounterEnumType.ICE) && !"Iceberg".equals(c.getName()))
|
|| (type.is(CounterEnumType.ICE) && !"Iceberg".equals(c.getName()))
|
||||||
// some lands does use Depletion as Storage Counter
|
// some lands does use Depletion as Storage Counter
|
||||||
|| (type.is(CounterEnumType.DEPLETION) && c.hasKeyword("CARDNAME doesn't untap during your untap step."))
|
|| (type.is(CounterEnumType.DEPLETION) && c.getReplacementEffects().anyMatch(r -> r.getMode().equals(ReplacementType.Untap) && r.getLayer().equals(ReplacementLayer.CantHappen)))
|
||||||
// treat Time Counters on suspended Cards as Bad,
|
// treat Time Counters on suspended Cards as Bad,
|
||||||
// and also on Chronozoa
|
// and also on Chronozoa
|
||||||
|| (type.is(CounterEnumType.TIME) && (!c.isInPlay() || "Chronozoa".equals(c.getName())))
|
|| (type.is(CounterEnumType.TIME) && (!c.isInPlay() || "Chronozoa".equals(c.getName())))
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import forge.game.replacement.ReplacementEffect;
|
|||||||
import forge.game.replacement.ReplacementLayer;
|
import forge.game.replacement.ReplacementLayer;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
|
import forge.game.staticability.StaticAbilityMode;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.zone.MagicStack;
|
import forge.game.zone.MagicStack;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
@@ -691,6 +692,8 @@ public class ComputerUtilCard {
|
|||||||
public static boolean canBeBlockedProfitably(final Player ai, Card attacker, boolean checkingOther) {
|
public static boolean canBeBlockedProfitably(final Player ai, Card attacker, boolean checkingOther) {
|
||||||
AiBlockController aiBlk = new AiBlockController(ai, checkingOther);
|
AiBlockController aiBlk = new AiBlockController(ai, checkingOther);
|
||||||
Combat combat = new Combat(ai);
|
Combat combat = new Combat(ai);
|
||||||
|
// avoid removing original attacker
|
||||||
|
attacker.setCombatLKI(null);
|
||||||
combat.addAttacker(attacker, ai);
|
combat.addAttacker(attacker, ai);
|
||||||
final List<Card> attackers = Lists.newArrayList(attacker);
|
final List<Card> attackers = Lists.newArrayList(attacker);
|
||||||
aiBlk.assignBlockersGivenAttackers(combat, attackers);
|
aiBlk.assignBlockersGivenAttackers(combat, attackers);
|
||||||
@@ -1211,8 +1214,7 @@ public class ComputerUtilCard {
|
|||||||
// if this thing is both owned and controlled by an opponent and it has a continuous ability,
|
// if this thing is both owned and controlled by an opponent and it has a continuous ability,
|
||||||
// assume it either benefits the player or disrupts the opponent
|
// assume it either benefits the player or disrupts the opponent
|
||||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||||
final Map<String, String> params = stAb.getMapParams();
|
if (stAb.checkMode(StaticAbilityMode.Continuous) && stAb.isIntrinsic()) {
|
||||||
if (params.get("Mode").equals("Continuous") && stAb.isIntrinsic()) {
|
|
||||||
priority = true;
|
priority = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1243,17 +1245,16 @@ public class ComputerUtilCard {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||||
final Map<String, String> params = stAb.getMapParams();
|
|
||||||
//continuous buffs
|
//continuous buffs
|
||||||
if (params.get("Mode").equals("Continuous") && "Creature.YouCtrl".equals(params.get("Affected"))) {
|
if (stAb.checkMode(StaticAbilityMode.Continuous) && "Creature.YouCtrl".equals(stAb.getParam("Affected"))) {
|
||||||
int bonusPT = 0;
|
int bonusPT = 0;
|
||||||
if (params.containsKey("AddPower")) {
|
if (stAb.hasParam("AddPower")) {
|
||||||
bonusPT += AbilityUtils.calculateAmount(c, params.get("AddPower"), stAb);
|
bonusPT += AbilityUtils.calculateAmount(c, stAb.getParam("AddPower"), stAb);
|
||||||
}
|
}
|
||||||
if (params.containsKey("AddToughness")) {
|
if (stAb.hasParam("AddToughness")) {
|
||||||
bonusPT += AbilityUtils.calculateAmount(c, params.get("AddPower"), stAb);
|
bonusPT += AbilityUtils.calculateAmount(c, stAb.getParam("AddPower"), stAb);
|
||||||
}
|
}
|
||||||
String kws = params.get("AddKeyword");
|
String kws = stAb.getParam("AddKeyword");
|
||||||
if (kws != null) {
|
if (kws != null) {
|
||||||
bonusPT += 4 * (1 + StringUtils.countMatches(kws, "&")); //treat each added keyword as a +2/+2 for now
|
bonusPT += 4 * (1 + StringUtils.countMatches(kws, "&")); //treat each added keyword as a +2/+2 for now
|
||||||
}
|
}
|
||||||
@@ -1735,11 +1736,6 @@ public class ComputerUtilCard {
|
|||||||
pumped.setPTBoost(c.getPTBoostTable());
|
pumped.setPTBoost(c.getPTBoostTable());
|
||||||
pumped.addPTBoost(power + berserkPower, toughness, timestamp, 0);
|
pumped.addPTBoost(power + berserkPower, toughness, timestamp, 0);
|
||||||
|
|
||||||
pumped.setSwitchPTTable(c.getSwitchPTTable());
|
|
||||||
if (sa.hasParam("SwitchPT")) {
|
|
||||||
pumped.addSwitchPT(timestamp, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!kws.isEmpty()) {
|
if (!kws.isEmpty()) {
|
||||||
pumped.addChangedCardKeywords(kws, null, false, timestamp, null, false);
|
pumped.addChangedCardKeywords(kws, null, false, timestamp, null, false);
|
||||||
}
|
}
|
||||||
@@ -1789,7 +1785,7 @@ public class ComputerUtilCard {
|
|||||||
// remove old boost that might be copied
|
// remove old boost that might be copied
|
||||||
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
for (final StaticAbility stAb : c.getStaticAbilities()) {
|
||||||
vCard.removePTBoost(c.getLayerTimestamp(), stAb.getId());
|
vCard.removePTBoost(c.getLayerTimestamp(), stAb.getId());
|
||||||
if (!stAb.checkMode("Continuous")) {
|
if (!stAb.checkMode(StaticAbilityMode.Continuous)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!stAb.hasParam("Affected")) {
|
if (!stAb.hasParam("Affected")) {
|
||||||
@@ -1867,7 +1863,7 @@ public class ComputerUtilCard {
|
|||||||
if (!c.isCreature()) {
|
if (!c.isCreature()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (c.hasKeyword("CARDNAME can't attack or block.") || (c.hasKeyword("CARDNAME doesn't untap during your untap step.") && c.isTapped()) || (c.getOwner() == ai && ai.getOpponents().contains(c.getController()))) {
|
if (c.hasKeyword("CARDNAME can't attack or block.") || (c.isTapped() && !c.canUntap(ai, true)) || (c.getOwner() == ai && ai.getOpponents().contains(c.getController()))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import forge.game.combat.Combat;
|
|||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.cost.CostPayment;
|
import forge.game.cost.CostPayment;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.Untap;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.replacement.ReplacementLayer;
|
import forge.game.replacement.ReplacementLayer;
|
||||||
@@ -39,6 +39,7 @@ import forge.game.replacement.ReplacementType;
|
|||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
|
import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked;
|
||||||
|
import forge.game.staticability.StaticAbilityMode;
|
||||||
import forge.game.staticability.StaticAbilityMustAttack;
|
import forge.game.staticability.StaticAbilityMustAttack;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
@@ -101,7 +102,7 @@ public class ComputerUtilCombat {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attacker.getGame().getReplacementHandler().wouldPhaseBeSkipped(attacker.getController(), "BeginCombat")) {
|
if (attacker.getGame().getReplacementHandler().wouldPhaseBeSkipped(attacker.getController(), PhaseType.COMBAT_BEGIN)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +119,7 @@ public class ComputerUtilCombat {
|
|||||||
// || (attacker.hasKeyword(Keyword.FADING) && attacker.getCounters(CounterEnumType.FADE) == 0)
|
// || (attacker.hasKeyword(Keyword.FADING) && attacker.getCounters(CounterEnumType.FADE) == 0)
|
||||||
// || attacker.hasSVar("EndOfTurnLeavePlay"));
|
// || attacker.hasSVar("EndOfTurnLeavePlay"));
|
||||||
// The creature won't untap next turn
|
// The creature won't untap next turn
|
||||||
return !attacker.isTapped() || (attacker.getCounters(CounterEnumType.STUN) == 0 && Untap.canUntap(attacker));
|
return !attacker.isTapped() || (attacker.getCounters(CounterEnumType.STUN) == 0 && attacker.canUntap(attacker.getController(), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -900,7 +901,7 @@ public class ComputerUtilCombat {
|
|||||||
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
||||||
for (final Card card : cardList) {
|
for (final Card card : cardList) {
|
||||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||||
if (!stAb.checkMode("Continuous")) {
|
if (!stAb.checkMode(StaticAbilityMode.Continuous)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("blocking")) {
|
if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("blocking")) {
|
||||||
@@ -1196,7 +1197,7 @@ public class ComputerUtilCombat {
|
|||||||
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command));
|
||||||
for (final Card card : cardList) {
|
for (final Card card : cardList) {
|
||||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||||
if (!stAb.checkMode("Continuous")) {
|
if (!stAb.checkMode(StaticAbilityMode.Continuous)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("attacking")) {
|
if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("attacking")) {
|
||||||
@@ -1387,7 +1388,7 @@ public class ComputerUtilCombat {
|
|||||||
final CardCollectionView cardList = game.getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView cardList = game.getCardsIn(ZoneType.Battlefield);
|
||||||
for (final Card card : cardList) {
|
for (final Card card : cardList) {
|
||||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||||
if (!"Continuous".equals(stAb.getParam("Mode"))) {
|
if (!stAb.checkMode(StaticAbilityMode.Continuous)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!stAb.hasParam("Affected")) {
|
if (!stAb.hasParam("Affected")) {
|
||||||
@@ -1734,6 +1735,7 @@ public class ComputerUtilCombat {
|
|||||||
final int attackerLife = getDamageToKill(attacker, false)
|
final int attackerLife = getDamageToKill(attacker, false)
|
||||||
+ predictToughnessBonusOfAttacker(attacker, blocker, combat, withoutAbilities, withoutAttackerStaticAbilities);
|
+ predictToughnessBonusOfAttacker(attacker, blocker, combat, withoutAbilities, withoutAttackerStaticAbilities);
|
||||||
|
|
||||||
|
// AI should be less worried about Deathtouch
|
||||||
if (blocker.hasDoubleStrike()) {
|
if (blocker.hasDoubleStrike()) {
|
||||||
if (defenderDamage > 0 && (hasKeyword(blocker, "Deathtouch", withoutAbilities, combat) || attacker.hasSVar("DestroyWhenDamaged"))) {
|
if (defenderDamage > 0 && (hasKeyword(blocker, "Deathtouch", withoutAbilities, combat) || attacker.hasSVar("DestroyWhenDamaged"))) {
|
||||||
return true;
|
return true;
|
||||||
@@ -1963,6 +1965,7 @@ public class ComputerUtilCombat {
|
|||||||
final int attackerLife = getDamageToKill(attacker, false)
|
final int attackerLife = getDamageToKill(attacker, false)
|
||||||
+ predictToughnessBonusOfAttacker(attacker, blocker, combat, withoutAbilities, withoutAttackerStaticAbilities);
|
+ predictToughnessBonusOfAttacker(attacker, blocker, combat, withoutAbilities, withoutAttackerStaticAbilities);
|
||||||
|
|
||||||
|
// AI should be less worried about deathtouch
|
||||||
if (attacker.hasDoubleStrike()) {
|
if (attacker.hasDoubleStrike()) {
|
||||||
if (attackerDamage >= defenderLife) {
|
if (attackerDamage >= defenderLife) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -608,7 +608,7 @@ public class ComputerUtilCost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ComputerUtilMana.canPayManaCost(cost, sa, player, extraManaNeeded, effect)
|
return ComputerUtilMana.canPayManaCost(cost, sa, player, extraManaNeeded, effect)
|
||||||
&& CostPayment.canPayAdditionalCosts(cost, sa, effect);
|
&& CostPayment.canPayAdditionalCosts(cost, sa, effect, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Set<String> getAvailableManaColors(Player ai, Card additionalLand) {
|
public static Set<String> getAvailableManaColors(Player ai, Card additionalLand) {
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mana abilities on the same card
|
// Mana abilities on the same card
|
||||||
String shardMana = shard.toString().replaceAll("\\{", "").replaceAll("\\}", "");
|
String shardMana = shard.toShortString();
|
||||||
|
|
||||||
boolean payWithAb1 = ability1.getManaPart().mana(ability1).contains(shardMana);
|
boolean payWithAb1 = ability1.getManaPart().mana(ability1).contains(shardMana);
|
||||||
boolean payWithAb2 = ability2.getManaPart().mana(ability2).contains(shardMana);
|
boolean payWithAb2 = ability2.getManaPart().mana(ability2).contains(shardMana);
|
||||||
@@ -642,24 +642,28 @@ public class ComputerUtilMana {
|
|||||||
List<SpellAbility> paymentList = Lists.newArrayList();
|
List<SpellAbility> paymentList = Lists.newArrayList();
|
||||||
final ManaPool manapool = ai.getManaPool();
|
final ManaPool manapool = ai.getManaPool();
|
||||||
|
|
||||||
// Apply the color/type conversion matrix if necessary
|
// Apply color/type conversion matrix if necessary (already done via autopay)
|
||||||
manapool.restoreColorReplacements();
|
if (ai.getControllingPlayer() == null) {
|
||||||
CardPlayOption mayPlay = sa.getMayPlayOption();
|
manapool.restoreColorReplacements();
|
||||||
if (!effect) {
|
CardPlayOption mayPlay = sa.getMayPlayOption();
|
||||||
if (sa.isSpell() && mayPlay != null) {
|
if (!effect) {
|
||||||
mayPlay.applyManaConvert(manapool);
|
if (sa.isSpell() && mayPlay != null) {
|
||||||
} else if (sa.isActivatedAbility() && sa.getGrantorStatic() != null && sa.getGrantorStatic().hasParam("ManaConversion")) {
|
mayPlay.applyManaConvert(manapool);
|
||||||
AbilityUtils.applyManaColorConversion(manapool, sa.getGrantorStatic().getParam("ManaConversion"));
|
} else if (sa.isActivatedAbility() && sa.getGrantorStatic() != null && sa.getGrantorStatic().hasParam("ManaConversion")) {
|
||||||
|
AbilityUtils.applyManaColorConversion(manapool, sa.getGrantorStatic().getParam("ManaConversion"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (sa.hasParam("ManaConversion")) {
|
||||||
|
AbilityUtils.applyManaColorConversion(manapool, sa.getParam("ManaConversion"));
|
||||||
|
}
|
||||||
|
StaticAbilityManaConvert.manaConvert(manapool, ai, sa.getHostCard(), effect && !sa.isCastFromPlayEffect() ? null : sa);
|
||||||
}
|
}
|
||||||
if (sa.hasParam("ManaConversion")) {
|
|
||||||
AbilityUtils.applyManaColorConversion(manapool, sa.getParam("ManaConversion"));
|
|
||||||
}
|
|
||||||
StaticAbilityManaConvert.manaConvert(manapool, ai, sa.getHostCard(), effect && !sa.isCastFromPlayEffect() ? null : sa);
|
|
||||||
|
|
||||||
|
// not worth checking if it makes sense to not spend floating first
|
||||||
if (manapool.payManaCostFromPool(cost, sa, test, manaSpentToPay)) {
|
if (manapool.payManaCostFromPool(cost, sa, test, manaSpentToPay)) {
|
||||||
CostPayment.handleOfferings(sa, test, cost.isPaid());
|
CostPayment.handleOfferings(sa, test, cost.isPaid());
|
||||||
return true; // paid all from floating mana
|
// paid all from floating mana
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean purePhyrexian = cost.containsOnlyPhyrexianMana();
|
boolean purePhyrexian = cost.containsOnlyPhyrexianMana();
|
||||||
|
|||||||
@@ -160,12 +160,6 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
|||||||
value += addValue(20, "protection");
|
value += addValue(20, "protection");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
|
||||||
if (sa.isAbility()) {
|
|
||||||
value += addValue(evaluateSpellAbility(sa), "sa: " + sa);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// paired creatures are more valuable because they grant a bonus to the other creature
|
// paired creatures are more valuable because they grant a bonus to the other creature
|
||||||
if (c.isPaired()) {
|
if (c.isPaired()) {
|
||||||
value += addValue(14, "paired");
|
value += addValue(14, "paired");
|
||||||
@@ -213,11 +207,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
|||||||
value += addValue(1, "untapped");
|
value += addValue(1, "untapped");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!c.getManaAbilities().isEmpty()) {
|
if (!c.canUntap(c.getController(), true)) {
|
||||||
value += addValue(10, "manadork");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
|
|
||||||
if (c.isTapped()) {
|
if (c.isTapped()) {
|
||||||
value = addValue(50 + (c.getCMC() * 5), "tapped-useless"); // reset everything - useless
|
value = addValue(50 + (c.getCMC() * 5), "tapped-useless"); // reset everything - useless
|
||||||
} else {
|
} else {
|
||||||
@@ -226,6 +216,17 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
|||||||
} else {
|
} else {
|
||||||
value -= subValue(10 * c.getCounters(CounterEnumType.STUN), "stunned");
|
value -= subValue(10 * c.getCounters(CounterEnumType.STUN), "stunned");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||||
|
if (sa.isAbility()) {
|
||||||
|
value += addValue(evaluateSpellAbility(sa), "sa: " + sa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!c.getManaAbilities().isEmpty()) {
|
||||||
|
value += addValue(10, "manadork");
|
||||||
|
}
|
||||||
|
|
||||||
// use scaling because the creature is only available halfway
|
// use scaling because the creature is only available halfway
|
||||||
if (c.hasKeyword(Keyword.PHASING)) {
|
if (c.hasKeyword(Keyword.PHASING)) {
|
||||||
value -= subValue(Math.max(20, value / 2), "phasing");
|
value -= subValue(Math.max(20, value / 2), "phasing");
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import forge.card.mana.ManaAtom;
|
|||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.ability.effects.DetachedCardEffect;
|
import forge.game.ability.effects.DetachedCardEffect;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.card.token.TokenInfo;
|
import forge.game.card.token.TokenInfo;
|
||||||
@@ -1305,10 +1306,10 @@ public abstract class GameState {
|
|||||||
} else if (info.startsWith("FaceDown")) {
|
} else if (info.startsWith("FaceDown")) {
|
||||||
c.turnFaceDown(true);
|
c.turnFaceDown(true);
|
||||||
if (info.endsWith("Manifested")) {
|
if (info.endsWith("Manifested")) {
|
||||||
c.setManifested(true);
|
c.setManifested(new SpellAbility.EmptySa(ApiType.Manifest, c));
|
||||||
}
|
}
|
||||||
if (info.endsWith("Cloaked")) {
|
if (info.endsWith("Cloaked")) {
|
||||||
c.setCloaked(true);
|
c.setCloaked(new SpellAbility.EmptySa(ApiType.Cloak, c));
|
||||||
}
|
}
|
||||||
} else if (info.startsWith("Transformed")) {
|
} else if (info.startsWith("Transformed")) {
|
||||||
c.setState(CardStateName.Transformed, true);
|
c.setState(CardStateName.Transformed, true);
|
||||||
@@ -1408,7 +1409,7 @@ public abstract class GameState {
|
|||||||
} else if (info.equals("Foretold")) {
|
} else if (info.equals("Foretold")) {
|
||||||
c.setForetold(true);
|
c.setForetold(true);
|
||||||
c.turnFaceDown(true);
|
c.turnFaceDown(true);
|
||||||
c.addMayLookTemp(c.getOwner());
|
c.addMayLookFaceDownExile(c.getOwner());
|
||||||
} else if (info.equals("ForetoldThisTurn")) {
|
} else if (info.equals("ForetoldThisTurn")) {
|
||||||
c.setTurnInZone(turn);
|
c.setTurnInZone(turn);
|
||||||
} else if (info.equals("IsToken")) {
|
} else if (info.equals("IsToken")) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import forge.game.*;
|
|||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.ability.effects.CharmEffect;
|
import forge.game.ability.effects.CharmEffect;
|
||||||
|
import forge.game.ability.effects.RollDiceEffect;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
@@ -745,6 +746,30 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
return Aggregates.random(rolls);
|
return Aggregates.random(rolls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Integer> chooseDiceToReroll(List<Integer> rolls) {
|
||||||
|
//TODO create AI logic for this
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer chooseRollToModify(List<Integer> rolls) {
|
||||||
|
//TODO create AI logic for this
|
||||||
|
return Aggregates.random(rolls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RollDiceEffect.DieRollResult chooseRollToSwap(List<RollDiceEffect.DieRollResult> rolls) {
|
||||||
|
//TODO create AI logic for this
|
||||||
|
return Aggregates.random(rolls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String chooseRollSwapValue(List<String> swapChoices, Integer currentResult, int power, int toughness) {
|
||||||
|
//TODO create AI logic for this
|
||||||
|
return Aggregates.random(swapChoices);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean mulliganKeepHand(Player firstPlayer, int cardsToReturn) {
|
public boolean mulliganKeepHand(Player firstPlayer, int cardsToReturn) {
|
||||||
return !ComputerUtil.wantMulligan(player, cardsToReturn);
|
return !ComputerUtil.wantMulligan(player, cardsToReturn);
|
||||||
@@ -1207,6 +1232,11 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean payCostDuringRoll(final Cost cost, final SpellAbility sa, final FCollectionView<Player> allPayers) {
|
||||||
|
// TODO logic for AI to pay rerolls and modification costs
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void orderAndPlaySimultaneousSa(List<SpellAbility> activePlayerSAs) {
|
public void orderAndPlaySimultaneousSa(List<SpellAbility> activePlayerSAs) {
|
||||||
for (final SpellAbility sa : getAi().orderPlaySa(activePlayerSAs)) {
|
for (final SpellAbility sa : getAi().orderPlaySa(activePlayerSAs)) {
|
||||||
|
|||||||
@@ -1469,6 +1469,7 @@ public class SpecialCardAi {
|
|||||||
if (best != null) {
|
if (best != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(best);
|
sa.getTargets().add(best);
|
||||||
|
sa.setXManaCostPaid(best.getCMC());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ public abstract class SpellAbilityAi {
|
|||||||
protected static boolean isSorcerySpeed(final SpellAbility sa, Player ai) {
|
protected static boolean isSorcerySpeed(final SpellAbility sa, Player ai) {
|
||||||
return (sa.getRootAbility().isSpell() && sa.getHostCard().isSorcery())
|
return (sa.getRootAbility().isSpell() && sa.getHostCard().isSorcery())
|
||||||
|| (sa.getRootAbility().isActivatedAbility() && sa.getRootAbility().getRestrictions().isSorcerySpeed())
|
|| (sa.getRootAbility().isActivatedAbility() && sa.getRootAbility().getRestrictions().isSorcerySpeed())
|
||||||
|| (sa.getRootAbility().isAdventure() && sa.getHostCard().getState(CardStateName.Adventure).getType().isSorcery())
|
|| (sa.getRootAbility().isAdventure() && sa.getHostCard().getState(CardStateName.Secondary).getType().isSorcery())
|
||||||
|| (sa.isPwAbility() && !sa.withFlash(sa.getHostCard(), ai));
|
|| (sa.isPwAbility() && !sa.withFlash(sa.getHostCard(), ai));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.EachDamage, DamageEachAi.class)
|
.put(ApiType.EachDamage, DamageEachAi.class)
|
||||||
.put(ApiType.Effect, EffectAi.class)
|
.put(ApiType.Effect, EffectAi.class)
|
||||||
.put(ApiType.Encode, EncodeAi.class)
|
.put(ApiType.Encode, EncodeAi.class)
|
||||||
|
.put(ApiType.Endure, EndureAi.class)
|
||||||
.put(ApiType.EndCombatPhase, EndTurnAi.class)
|
.put(ApiType.EndCombatPhase, EndTurnAi.class)
|
||||||
.put(ApiType.EndTurn, EndTurnAi.class)
|
.put(ApiType.EndTurn, EndTurnAi.class)
|
||||||
.put(ApiType.ExchangeLife, LifeExchangeAi.class)
|
.put(ApiType.ExchangeLife, LifeExchangeAi.class)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import forge.game.spellability.SpellAbility;
|
|||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.staticability.StaticAbilityContinuous;
|
import forge.game.staticability.StaticAbilityContinuous;
|
||||||
import forge.game.staticability.StaticAbilityLayer;
|
import forge.game.staticability.StaticAbilityLayer;
|
||||||
|
import forge.game.staticability.StaticAbilityMode;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.FileSection;
|
import forge.util.FileSection;
|
||||||
import forge.util.collect.FCollectionView;
|
import forge.util.collect.FCollectionView;
|
||||||
@@ -562,7 +563,7 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
CardTraitChanges traits = card.getChangedCardTraits().get(timestamp, 0);
|
CardTraitChanges traits = card.getChangedCardTraits().get(timestamp, 0);
|
||||||
if (traits != null) {
|
if (traits != null) {
|
||||||
for (StaticAbility stAb : traits.getStaticAbilities()) {
|
for (StaticAbility stAb : traits.getStaticAbilities()) {
|
||||||
if ("Continuous".equals(stAb.getParam("Mode"))) {
|
if (stAb.checkMode(StaticAbilityMode.Continuous)) {
|
||||||
for (final StaticAbilityLayer layer : stAb.getLayers()) {
|
for (final StaticAbilityLayer layer : stAb.getLayers()) {
|
||||||
StaticAbilityContinuous.applyContinuousAbility(stAb, new CardCollection(card), layer);
|
StaticAbilityContinuous.applyContinuousAbility(stAb, new CardCollection(card), layer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,20 +15,23 @@ import forge.game.cost.Cost;
|
|||||||
import forge.game.cost.CostPart;
|
import forge.game.cost.CostPart;
|
||||||
import forge.game.cost.CostSacrifice;
|
import forge.game.cost.CostSacrifice;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
|
import forge.game.keyword.KeywordInterface;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.replacement.ReplacementLayer;
|
||||||
|
import forge.game.replacement.ReplacementType;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.staticability.StaticAbilityCantAttackBlock;
|
import forge.game.staticability.StaticAbilityCantAttackBlock;
|
||||||
|
import forge.game.staticability.StaticAbilityMode;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -72,7 +75,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// prevent run-away activations - first time will always return true
|
// prevent run-away activations - first time will always return true
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach spells always have a target
|
// Attach spells always have a target
|
||||||
@@ -130,7 +133,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
int power = 0, toughness = 0;
|
int power = 0, toughness = 0;
|
||||||
List<String> keywords = Lists.newArrayList();
|
List<String> keywords = Lists.newArrayList();
|
||||||
for (StaticAbility stAb : source.getStaticAbilities()) {
|
for (StaticAbility stAb : source.getStaticAbilities()) {
|
||||||
if ("Continuous".equals(stAb.getParam("Mode"))) {
|
if (stAb.checkMode(StaticAbilityMode.Continuous)) {
|
||||||
if (stAb.hasParam("AddPower")) {
|
if (stAb.hasParam("AddPower")) {
|
||||||
power += AbilityUtils.calculateAmount(source, stAb.getParam("AddPower"), stAb);
|
power += AbilityUtils.calculateAmount(source, stAb.getParam("AddPower"), stAb);
|
||||||
}
|
}
|
||||||
@@ -307,9 +310,8 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
String type = "";
|
String type = "";
|
||||||
|
|
||||||
for (final StaticAbility stAb : attachSource.getStaticAbilities()) {
|
for (final StaticAbility stAb : attachSource.getStaticAbilities()) {
|
||||||
final Map<String, String> stab = stAb.getMapParams();
|
if (stAb.checkMode(StaticAbilityMode.Continuous) && stAb.hasParam("AddType")) {
|
||||||
if (stab.get("Mode").equals("Continuous") && stab.containsKey("AddType")) {
|
type = stAb.getParam("AddType");
|
||||||
type = stab.get("AddType");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,9 +373,39 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
private static Card attachAIKeepTappedPreference(final SpellAbility sa, final List<Card> list, final boolean mandatory, final Card attachSource) {
|
private static Card attachAIKeepTappedPreference(final SpellAbility sa, final List<Card> list, final boolean mandatory, final Card attachSource) {
|
||||||
// AI For Cards like Paralyzing Grasp and Glimmerdust Nap
|
// AI For Cards like Paralyzing Grasp and Glimmerdust Nap
|
||||||
|
|
||||||
|
// check for ETB Trigger
|
||||||
|
boolean tapETB = isAuraSpell(sa) && attachSource.getTriggers().anyMatch(t -> {
|
||||||
|
if (t.getMode() != TriggerType.ChangesZone) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ZoneType.Battlefield.toString().equals(t.getParam("Destination"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.hasParam("ValidCard") && !t.getParam("ValidCard").contains("Self")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpellAbility tSa = t.ensureAbility();
|
||||||
|
if (tSa == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ApiType.Tap.equals(tSa.getApi())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!"Enchanted".equals(tSa.getParam("Defined"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
final List<Card> prefList = CardLists.filter(list, c -> {
|
final List<Card> prefList = CardLists.filter(list, c -> {
|
||||||
// Don't do Untapped Vigilance cards
|
// Don't do Untapped Vigilance cards
|
||||||
if (c.isCreature() && c.hasKeyword(Keyword.VIGILANCE) && c.isUntapped()) {
|
if (!tapETB && c.isCreature() && c.hasKeyword(Keyword.VIGILANCE) && c.isUntapped()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,20 +420,9 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// already affected
|
||||||
if (!c.isEnchanted()) {
|
if (!c.canUntap(c.getController(), true)) {
|
||||||
return true;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
final Iterable<Card> auras = c.getEnchantedBy();
|
|
||||||
for (Card aura : auras) {
|
|
||||||
SpellAbility auraSA = aura.getSpells().get(0);
|
|
||||||
if (auraSA.getApi() == ApiType.Attach) {
|
|
||||||
if ("KeepTapped".equals(auraSA.getParam("AILogic"))) {
|
|
||||||
// Don't attach multiple KeepTapped Auras to one card
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -486,29 +507,29 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
private static Card attachAIAnimatePreference(final SpellAbility sa, final List<Card> list, final boolean mandatory,
|
private static Card attachAIAnimatePreference(final SpellAbility sa, final List<Card> list, final boolean mandatory,
|
||||||
final Card attachSource) {
|
final Card attachSource) {
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Card card = null;
|
Card card = null;
|
||||||
// AI For choosing a Card to Animate.
|
// AI For choosing a Card to Animate.
|
||||||
List<Card> betterList = CardLists.getNotType(list, "Creature");
|
List<Card> betterList = CardLists.getNotType(list, "Creature");
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Animate Artifact")) {
|
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Animate Artifact")) {
|
||||||
betterList = CardLists.filter(betterList, c -> c.getCMC() > 0);
|
betterList = CardLists.filter(betterList, c -> c.getCMC() > 0);
|
||||||
card = ComputerUtilCard.getMostExpensivePermanentAI(betterList);
|
card = ComputerUtilCard.getMostExpensivePermanentAI(betterList);
|
||||||
} else {
|
} else {
|
||||||
List<Card> evenBetterList = CardLists.filter(betterList, c -> c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.hasKeyword(Keyword.HEXPROOF));
|
List<Card> evenBetterList = CardLists.filter(betterList, c -> c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.hasKeyword(Keyword.HEXPROOF));
|
||||||
if (!evenBetterList.isEmpty()) {
|
if (!evenBetterList.isEmpty()) {
|
||||||
betterList = evenBetterList;
|
betterList = evenBetterList;
|
||||||
}
|
}
|
||||||
evenBetterList = CardLists.filter(betterList, CardPredicates.UNTAPPED);
|
evenBetterList = CardLists.filter(betterList, CardPredicates.UNTAPPED);
|
||||||
if (!evenBetterList.isEmpty()) {
|
if (!evenBetterList.isEmpty()) {
|
||||||
betterList = evenBetterList;
|
betterList = evenBetterList;
|
||||||
}
|
}
|
||||||
evenBetterList = CardLists.filter(betterList, c -> c.getTurnInZone() != c.getGame().getPhaseHandler().getTurn());
|
evenBetterList = CardLists.filter(betterList, c -> c.getTurnInZone() != c.getGame().getPhaseHandler().getTurn());
|
||||||
if (!evenBetterList.isEmpty()) {
|
if (!evenBetterList.isEmpty()) {
|
||||||
betterList = evenBetterList;
|
betterList = evenBetterList;
|
||||||
}
|
}
|
||||||
evenBetterList = CardLists.filter(betterList, c -> {
|
evenBetterList = CardLists.filter(betterList, c -> {
|
||||||
for (final SpellAbility sa1 : c.getSpellAbilities()) {
|
for (final SpellAbility sa1 : c.getSpellAbilities()) {
|
||||||
if (sa1.isAbility() && sa1.getPayCosts().hasTapCost()) {
|
if (sa1.isAbility() && sa1.getPayCosts().hasTapCost()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -516,10 +537,10 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
if (!evenBetterList.isEmpty()) {
|
if (!evenBetterList.isEmpty()) {
|
||||||
betterList = evenBetterList;
|
betterList = evenBetterList;
|
||||||
}
|
}
|
||||||
card = ComputerUtilCard.getWorstAI(betterList);
|
card = ComputerUtilCard.getWorstAI(betterList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -549,28 +570,46 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
final Card attachSource) {
|
final Card attachSource) {
|
||||||
// AI For choosing a Card to Animate.
|
// AI For choosing a Card to Animate.
|
||||||
final Player ai = sa.getActivatingPlayer();
|
final Player ai = sa.getActivatingPlayer();
|
||||||
final Card attachSourceLki = CardCopyService.getLKICopy(attachSource);
|
Card attachSourceLki = null;
|
||||||
|
for (Trigger t : attachSource.getTriggers()) {
|
||||||
|
if (!t.getMode().equals(TriggerType.ChangesZone)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!"Battlefield".equals(t.getParam("Destination"))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!"Card.Self".equals(t.getParam("ValidCard"))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SpellAbility trigSa = t.ensureAbility();
|
||||||
|
SpellAbility animateSa = trigSa.findSubAbilityByType(ApiType.Animate);
|
||||||
|
if (animateSa == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
animateSa.setActivatingPlayer(sa.getActivatingPlayer());
|
||||||
|
attachSourceLki = AnimateAi.becomeAnimated(attachSource, animateSa);
|
||||||
|
}
|
||||||
|
if (attachSourceLki == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
attachSourceLki.setLastKnownZone(ai.getZone(ZoneType.Battlefield));
|
attachSourceLki.setLastKnownZone(ai.getZone(ZoneType.Battlefield));
|
||||||
// Suppress original attach Spell to replace it with another
|
final Card finalAttachSourceLki = attachSourceLki;
|
||||||
attachSourceLki.getFirstAttachSpell().setSuppressed(true);
|
|
||||||
|
|
||||||
//TODO for Reanimate Auras i need the new Attach Spell, in later versions it might be part of the Enchant Keyword
|
|
||||||
attachSourceLki.addSpellAbility(AbilityFactory.getAbility(attachSourceLki, "NewAttach"));
|
|
||||||
List<Card> betterList = CardLists.filter(list, c -> {
|
List<Card> betterList = CardLists.filter(list, c -> {
|
||||||
final Card lki = CardCopyService.getLKICopy(c);
|
final Card lki = CardCopyService.getLKICopy(c);
|
||||||
// need to fake it as if lki would be on the battlefield
|
// need to fake it as if lki would be on the battlefield
|
||||||
lki.setLastKnownZone(ai.getZone(ZoneType.Battlefield));
|
lki.setLastKnownZone(ai.getZone(ZoneType.Battlefield));
|
||||||
|
|
||||||
// Reanimate Auras use "Enchant creature put onto the battlefield with CARDNAME" with Remembered
|
// Reanimate Auras use "Enchant creature put onto the battlefield with CARDNAME" with Remembered
|
||||||
attachSourceLki.clearRemembered();
|
finalAttachSourceLki.clearRemembered();
|
||||||
attachSourceLki.addRemembered(lki);
|
finalAttachSourceLki.addRemembered(lki);
|
||||||
|
|
||||||
// need to check what the cards would be on the battlefield
|
// need to check what the cards would be on the battlefield
|
||||||
// do not attach yet, that would cause Events
|
// do not attach yet, that would cause Events
|
||||||
CardCollection preList = new CardCollection(lki);
|
CardCollection preList = new CardCollection(lki);
|
||||||
preList.add(attachSourceLki);
|
preList.add(finalAttachSourceLki);
|
||||||
c.getGame().getAction().checkStaticAbilities(false, Sets.newHashSet(preList), preList);
|
c.getGame().getAction().checkStaticAbilities(false, Sets.newHashSet(preList), preList);
|
||||||
boolean result = lki.canBeAttached(attachSourceLki, null);
|
boolean result = lki.canBeAttached(finalAttachSourceLki, null);
|
||||||
|
|
||||||
//reset static abilities
|
//reset static abilities
|
||||||
c.getGame().getAction().checkStaticAbilities(false);
|
c.getGame().getAction().checkStaticAbilities(false);
|
||||||
@@ -795,27 +834,45 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
int totPower = 0;
|
int totPower = 0;
|
||||||
final List<String> keywords = new ArrayList<>();
|
final List<String> keywords = new ArrayList<>();
|
||||||
|
|
||||||
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
|
boolean cantAttack = false;
|
||||||
final Map<String, String> stabMap = stAbility.getMapParams();
|
boolean cantBlock = false;
|
||||||
|
|
||||||
if (!stabMap.get("Mode").equals("Continuous")) {
|
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
|
||||||
|
if (stAbility.checkMode(StaticAbilityMode.CantAttack)) {
|
||||||
|
String valid = stAbility.getParam("ValidCard");
|
||||||
|
if (valid.contains(stCheck) || valid.contains("AttachedBy")) {
|
||||||
|
cantAttack = true;
|
||||||
|
}
|
||||||
|
} else if (stAbility.checkMode(StaticAbilityMode.CantBlock)) {
|
||||||
|
String valid = stAbility.getParam("ValidCard");
|
||||||
|
if (valid.contains(stCheck) || valid.contains("AttachedBy")) {
|
||||||
|
cantBlock = true;
|
||||||
|
}
|
||||||
|
} else if (stAbility.checkMode(StaticAbilityMode.CantBlockBy)) {
|
||||||
|
String valid = stAbility.getParam("ValidBlocker");
|
||||||
|
if (valid.contains(stCheck) || valid.contains("AttachedBy")) {
|
||||||
|
cantBlock = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stAbility.checkMode(StaticAbilityMode.Continuous)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String affected = stabMap.get("Affected");
|
final String affected = stAbility.getParam("Affected");
|
||||||
|
|
||||||
if (affected == null) {
|
if (affected == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ((affected.contains(stCheck) || affected.contains("AttachedBy"))) {
|
if ((affected.contains(stCheck) || affected.contains("AttachedBy"))) {
|
||||||
totToughness += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddToughness"), sa);
|
totToughness += AbilityUtils.calculateAmount(attachSource, stAbility.getParam("AddToughness"), sa);
|
||||||
totPower += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddPower"), sa);
|
totPower += AbilityUtils.calculateAmount(attachSource, stAbility.getParam("AddPower"), sa);
|
||||||
|
|
||||||
String kws = stabMap.get("AddKeyword");
|
String kws = stAbility.getParam("AddKeyword");
|
||||||
if (kws != null) {
|
if (kws != null) {
|
||||||
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
||||||
}
|
}
|
||||||
kws = stabMap.get("AddHiddenKeyword");
|
kws = stAbility.getParam("AddHiddenKeyword");
|
||||||
if (kws != null) {
|
if (kws != null) {
|
||||||
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
||||||
}
|
}
|
||||||
@@ -851,6 +908,12 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
prefList = CardLists.filter(prefList, c -> c.getNetPower() > 0 && ComputerUtilCombat.canAttackNextTurn(c));
|
prefList = CardLists.filter(prefList, c -> c.getNetPower() > 0 && ComputerUtilCombat.canAttackNextTurn(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cantAttack) {
|
||||||
|
prefList = CardLists.filter(prefList, c -> c.isCreature() && ComputerUtilCombat.canAttackNextTurn(c));
|
||||||
|
} else if (cantBlock) { // TODO better can block filter?
|
||||||
|
prefList = CardLists.filter(prefList, c -> c.isCreature() && !ComputerUtilCard.isUselessCreature(ai, c));
|
||||||
|
}
|
||||||
|
|
||||||
//some auras aren't useful in multiples
|
//some auras aren't useful in multiples
|
||||||
if (attachSource.hasSVar("NonStackingAttachEffect")) {
|
if (attachSource.hasSVar("NonStackingAttachEffect")) {
|
||||||
prefList = CardLists.filter(prefList,
|
prefList = CardLists.filter(prefList,
|
||||||
@@ -925,6 +988,10 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isAuraSpell(final SpellAbility sa) {
|
||||||
|
return sa.isSpell() && sa.getHostCard().isAura();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach preference.
|
* Attach preference.
|
||||||
*
|
*
|
||||||
@@ -940,7 +1007,23 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
private static boolean attachPreference(final SpellAbility sa, final TargetRestrictions tgt, final boolean mandatory) {
|
private static boolean attachPreference(final SpellAbility sa, final TargetRestrictions tgt, final boolean mandatory) {
|
||||||
GameObject o;
|
GameObject o;
|
||||||
if (tgt.canTgtPlayer()) {
|
boolean spellCanTargetPlayer = false;
|
||||||
|
if (isAuraSpell(sa)) {
|
||||||
|
Card source = sa.getHostCard();
|
||||||
|
if (!source.hasKeyword(Keyword.ENCHANT)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (KeywordInterface ki : source.getKeywords(Keyword.ENCHANT)) {
|
||||||
|
String ko = ki.getOriginal();
|
||||||
|
String m[] = ko.split(":");
|
||||||
|
String v = m[1];
|
||||||
|
if (v.contains("Player") || v.contains("Opponent")) {
|
||||||
|
spellCanTargetPlayer = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tgt.canTgtPlayer() && (!isAuraSpell(sa) || spellCanTargetPlayer)) {
|
||||||
List<Player> targetable = new ArrayList<>();
|
List<Player> targetable = new ArrayList<>();
|
||||||
for (final Player player : sa.getHostCard().getGame().getPlayers()) {
|
for (final Player player : sa.getHostCard().getGame().getPlayers()) {
|
||||||
if (sa.canTarget(player)) {
|
if (sa.canTarget(player)) {
|
||||||
@@ -1005,9 +1088,8 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
CardCollection toRemove = new CardCollection();
|
CardCollection toRemove = new CardCollection();
|
||||||
for (Trigger t : attachSource.getTriggers()) {
|
for (Trigger t : attachSource.getTriggers()) {
|
||||||
if (t.getMode() == TriggerType.ChangesZone) {
|
if (t.getMode() == TriggerType.ChangesZone) {
|
||||||
final Map<String, String> params = t.getMapParams();
|
if ("Card.Self".equals(t.getParam("ValidCard"))
|
||||||
if ("Card.Self".equals(params.get("ValidCard"))
|
&& "Battlefield".equals(t.getParam("Destination"))) {
|
||||||
&& "Battlefield".equals(params.get("Destination"))) {
|
|
||||||
SpellAbility trigSa = t.ensureAbility();
|
SpellAbility trigSa = t.ensureAbility();
|
||||||
if (trigSa != null && trigSa.getApi() == ApiType.DealDamage && "Enchanted".equals(trigSa.getParam("Defined"))) {
|
if (trigSa != null && trigSa.getApi() == ApiType.DealDamage && "Enchanted".equals(trigSa.getParam("Defined"))) {
|
||||||
for (Card target : list) {
|
for (Card target : list) {
|
||||||
@@ -1044,17 +1126,17 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
// Probably want to "weight" the list by amount of Enchantments and
|
// Probably want to "weight" the list by amount of Enchantments and
|
||||||
// choose the "lightest"
|
// choose the "lightest"
|
||||||
|
|
||||||
List<Card> betterList = CardLists.filter(magnetList, c -> CombatUtil.canAttack(c, ai.getWeakestOpponent()));
|
List<Card> betterList = CardLists.filter(magnetList, c -> CombatUtil.canAttack(c, ai.getWeakestOpponent()));
|
||||||
if (!betterList.isEmpty()) {
|
if (!betterList.isEmpty()) {
|
||||||
return ComputerUtilCard.getBestAI(betterList);
|
return ComputerUtilCard.getBestAI(betterList);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Magnet List should not be attached when they are useless
|
// Magnet List should not be attached when they are useless
|
||||||
betterList = CardLists.filter(magnetList, c -> !ComputerUtilCard.isUselessCreature(ai, c));
|
betterList = CardLists.filter(magnetList, c -> !ComputerUtilCard.isUselessCreature(ai, c));
|
||||||
|
|
||||||
if (!betterList.isEmpty()) {
|
if (!betterList.isEmpty()) {
|
||||||
return ComputerUtilCard.getBestAI(betterList);
|
return ComputerUtilCard.getBestAI(betterList);
|
||||||
}
|
}
|
||||||
|
|
||||||
//return ComputerUtilCard.getBestAI(magnetList);
|
//return ComputerUtilCard.getBestAI(magnetList);
|
||||||
}
|
}
|
||||||
@@ -1067,29 +1149,27 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
boolean grantingExtraBlock = false;
|
boolean grantingExtraBlock = false;
|
||||||
|
|
||||||
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
|
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
|
||||||
final Map<String, String> stabMap = stAbility.getMapParams();
|
if (!stAbility.checkMode(StaticAbilityMode.Continuous)) {
|
||||||
|
|
||||||
if (!"Continuous".equals(stabMap.get("Mode"))) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String affected = stabMap.get("Affected");
|
final String affected = stAbility.getParam("Affected");
|
||||||
|
|
||||||
if (affected == null) {
|
if (affected == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (affected.contains(stCheck) || affected.contains("AttachedBy")) {
|
if (affected.contains(stCheck) || affected.contains("AttachedBy")) {
|
||||||
totToughness += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddToughness"), stAbility);
|
totToughness += AbilityUtils.calculateAmount(attachSource, stAbility.getParam("AddToughness"), stAbility);
|
||||||
totPower += AbilityUtils.calculateAmount(attachSource, stabMap.get("AddPower"), stAbility);
|
totPower += AbilityUtils.calculateAmount(attachSource, stAbility.getParam("AddPower"), stAbility);
|
||||||
|
|
||||||
grantingAbilities |= stabMap.containsKey("AddAbility");
|
grantingAbilities |= stAbility.hasParam("AddAbility");
|
||||||
grantingExtraBlock |= stabMap.containsKey("CanBlockAmount") || stabMap.containsKey("CanBlockAny");
|
grantingExtraBlock |= stAbility.hasParam("CanBlockAmount") || stAbility.hasParam("CanBlockAny");
|
||||||
|
|
||||||
String kws = stabMap.get("AddKeyword");
|
String kws = stAbility.getParam("AddKeyword");
|
||||||
if (kws != null) {
|
if (kws != null) {
|
||||||
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
||||||
}
|
}
|
||||||
kws = stabMap.get("AddHiddenKeyword");
|
kws = stAbility.getParam("AddHiddenKeyword");
|
||||||
if (kws != null) {
|
if (kws != null) {
|
||||||
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
keywords.addAll(Arrays.asList(kws.split(" & ")));
|
||||||
}
|
}
|
||||||
@@ -1143,13 +1223,13 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
prefList = ComputerUtil.getSafeTargets(ai, sa, prefList);
|
prefList = ComputerUtil.getSafeTargets(ai, sa, prefList);
|
||||||
|
|
||||||
if (attachSource.isAura()) {
|
if (attachSource.isAura()) {
|
||||||
if (!attachSource.getName().equals("Daybreak Coronet")) {
|
if (!attachSource.getName().equals("Daybreak Coronet")) {
|
||||||
// TODO For Auras like Rancor, that aren't as likely to lead to
|
// TODO For Auras like Rancor, that aren't as likely to lead to
|
||||||
// card disadvantage, this check should be skipped
|
// card disadvantage, this check should be skipped
|
||||||
prefList = CardLists.filter(prefList, c -> !c.isEnchanted() || c.hasKeyword(Keyword.HEXPROOF));
|
prefList = CardLists.filter(prefList, c -> !c.isEnchanted() || c.hasKeyword(Keyword.HEXPROOF));
|
||||||
}
|
}
|
||||||
|
|
||||||
// should not attach Auras to creatures that does leave the play
|
// should not attach Auras to creatures that does leave the play
|
||||||
prefList = CardLists.filter(prefList, c -> !c.hasSVar("EndOfTurnLeavePlay"));
|
prefList = CardLists.filter(prefList, c -> !c.hasSVar("EndOfTurnLeavePlay"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1158,10 +1238,15 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
// TODO Somehow test for definitive advantage (e.g. opponent low on health, AI is attacking)
|
// TODO Somehow test for definitive advantage (e.g. opponent low on health, AI is attacking)
|
||||||
// to be able to deal the final blow with an enchanted vehicle like that
|
// to be able to deal the final blow with an enchanted vehicle like that
|
||||||
boolean canOnlyTargetCreatures = true;
|
boolean canOnlyTargetCreatures = true;
|
||||||
for (String valid : ObjectUtils.firstNonNull(attachSource.getFirstAttachSpell(), sa).getTargetRestrictions().getValidTgts()) {
|
if (attachSource.isAura()) {
|
||||||
if (!valid.startsWith("Creature")) {
|
for (KeywordInterface ki : attachSource.getKeywords(Keyword.ENCHANT)) {
|
||||||
canOnlyTargetCreatures = false;
|
String o = ki.getOriginal();
|
||||||
break;
|
String m[] = o.split(":");
|
||||||
|
String v = m[1];
|
||||||
|
if (!v.startsWith("Creature")) {
|
||||||
|
canOnlyTargetCreatures = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (canOnlyTargetCreatures && (attachSource.isAura() || attachSource.isEquipment())) {
|
if (canOnlyTargetCreatures && (attachSource.isAura() || attachSource.isEquipment())) {
|
||||||
@@ -1172,7 +1257,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
// Probably prefer to Enchant Creatures that Can Attack
|
// Probably prefer to Enchant Creatures that Can Attack
|
||||||
// Filter out creatures that can't Attack or have Defender
|
// Filter out creatures that can't Attack or have Defender
|
||||||
if (keywords.isEmpty()) {
|
if (keywords.isEmpty()) {
|
||||||
final int powerBonus = totPower;
|
final int powerBonus = totPower;
|
||||||
prefList = CardLists.filter(prefList, c -> {
|
prefList = CardLists.filter(prefList, c -> {
|
||||||
if (!c.isCreature()) {
|
if (!c.isCreature()) {
|
||||||
return true;
|
return true;
|
||||||
@@ -1387,8 +1472,6 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
c = attachAICuriosityPreference(sa, prefList, mandatory, attachSource);
|
c = attachAICuriosityPreference(sa, prefList, mandatory, attachSource);
|
||||||
} else if ("ChangeType".equals(logic)) {
|
} else if ("ChangeType".equals(logic)) {
|
||||||
c = attachAIChangeTypePreference(sa, prefList, mandatory, attachSource);
|
c = attachAIChangeTypePreference(sa, prefList, mandatory, attachSource);
|
||||||
} else if ("KeepTapped".equals(logic)) {
|
|
||||||
c = attachAIKeepTappedPreference(sa, prefList, mandatory, attachSource);
|
|
||||||
} else if ("Animate".equals(logic)) {
|
} else if ("Animate".equals(logic)) {
|
||||||
c = attachAIAnimatePreference(sa, prefList, mandatory, attachSource);
|
c = attachAIAnimatePreference(sa, prefList, mandatory, attachSource);
|
||||||
} else if ("Reanimate".equals(logic)) {
|
} else if ("Reanimate".equals(logic)) {
|
||||||
@@ -1399,6 +1482,12 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
c = attachAIHighestEvaluationPreference(prefList);
|
c = attachAIHighestEvaluationPreference(prefList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isAuraSpell(sa)) {
|
||||||
|
if (attachSource.getReplacementEffects().anyMatch(re -> re.getMode().equals(ReplacementType.Untap) && re.getLayer().equals(ReplacementLayer.CantHappen))) {
|
||||||
|
c = attachAIKeepTappedPreference(sa, prefList, mandatory, attachSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Consider exceptional cases which break the normal evaluation rules
|
// Consider exceptional cases which break the normal evaluation rules
|
||||||
if (!isUsefulAttachAction(ai, c, sa)) {
|
if (!isUsefulAttachAction(ai, c, sa)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -1551,8 +1640,6 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
} else if (keyword.endsWith("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|
} else if (keyword.endsWith("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|
||||||
|| keyword.endsWith("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
|
|| keyword.endsWith("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
|
||||||
return card.getNetCombatDamage() >= 2 && ComputerUtilCombat.canAttackNextTurn(card);
|
return card.getNetCombatDamage() >= 2 && ComputerUtilCombat.canAttackNextTurn(card);
|
||||||
} else if (keyword.endsWith("CARDNAME doesn't untap during your untap step.")) {
|
|
||||||
return !card.isUntapped();
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -914,6 +914,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (sa.isSpell()) {
|
if (sa.isSpell()) {
|
||||||
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// list = CardLists.canSubsequentlyTarget(list, sa);
|
||||||
|
|
||||||
if (sa.hasParam("AttachedTo")) {
|
if (sa.hasParam("AttachedTo")) {
|
||||||
list = CardLists.filter(list, c -> {
|
list = CardLists.filter(list, c -> {
|
||||||
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
@@ -1282,7 +1285,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
list.remove(choice);
|
list.remove(choice);
|
||||||
sa.getTargets().add(choice);
|
if (sa.canTarget(choice)) {
|
||||||
|
sa.getTargets().add(choice);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Honor the Single Zone restriction. For now, simply remove targets that do not belong to the same zone as the first targeted card.
|
// Honor the Single Zone restriction. For now, simply remove targets that do not belong to the same zone as the first targeted card.
|
||||||
@@ -1448,6 +1453,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
// AI Targeting
|
// AI Targeting
|
||||||
Card choice = null;
|
Card choice = null;
|
||||||
|
|
||||||
|
// Filter out cards TargetsForEachPlayer
|
||||||
|
list = CardLists.canSubsequentlyTarget(list, sa);
|
||||||
|
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
Card mostExpensivePermanent = ComputerUtilCard.getMostExpensivePermanentAI(list);
|
Card mostExpensivePermanent = ComputerUtilCard.getMostExpensivePermanentAI(list);
|
||||||
if (mostExpensivePermanent.isCreature()
|
if (mostExpensivePermanent.isCreature()
|
||||||
|
|||||||
@@ -161,10 +161,10 @@ public class ChooseGenericAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// FatespinnerSkipDraw,FatespinnerSkipMain,FatespinnerSkipCombat
|
// FatespinnerSkipDraw,FatespinnerSkipMain,FatespinnerSkipCombat
|
||||||
if (game.getReplacementHandler().wouldPhaseBeSkipped(player, "Draw")) {
|
if (game.getReplacementHandler().wouldPhaseBeSkipped(player, PhaseType.DRAW)) {
|
||||||
return skipDraw;
|
return skipDraw;
|
||||||
}
|
}
|
||||||
if (game.getReplacementHandler().wouldPhaseBeSkipped(player, "BeginCombat")) {
|
if (game.getReplacementHandler().wouldPhaseBeSkipped(player, PhaseType.COMBAT_BEGIN)) {
|
||||||
return skipCombat;
|
return skipCombat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public class CloakAi extends ManifestBaseAi {
|
|||||||
// (e.g. Grafdigger's Cage)
|
// (e.g. Grafdigger's Cage)
|
||||||
Card topCopy = CardCopyService.getLKICopy(card);
|
Card topCopy = CardCopyService.getLKICopy(card);
|
||||||
topCopy.turnFaceDownNoUpdate();
|
topCopy.turnFaceDownNoUpdate();
|
||||||
topCopy.setCloaked(true);
|
topCopy.setCloaked(sa);
|
||||||
|
|
||||||
if (ComputerUtil.isETBprevented(topCopy)) {
|
if (ComputerUtil.isETBprevented(topCopy)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import forge.ai.ComputerUtil;
|
|||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtilMana;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
@@ -18,6 +19,13 @@ public class ConniveAi extends SpellAbilityAi {
|
|||||||
return false; // can't draw anything
|
return false; // can't draw anything
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Card host = sa.getHostCard();
|
||||||
|
|
||||||
|
final int num = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("ConniveNum", "1"), sa);
|
||||||
|
if (num == 0) {
|
||||||
|
return false; // Won't do anything
|
||||||
|
}
|
||||||
|
|
||||||
CardCollection list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
|
||||||
// Filter AI-specific targets if provided
|
// Filter AI-specific targets if provided
|
||||||
|
|||||||
@@ -205,6 +205,9 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
while (t == null) {
|
while (t == null) {
|
||||||
// filter by MustTarget requirement
|
// filter by MustTarget requirement
|
||||||
CardCollection originalList = new CardCollection(list);
|
CardCollection originalList = new CardCollection(list);
|
||||||
|
|
||||||
|
list = CardLists.canSubsequentlyTarget(list, sa);
|
||||||
|
|
||||||
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
|
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
|
||||||
|
|
||||||
if (planeswalkers > 0) {
|
if (planeswalkers > 0) {
|
||||||
|
|||||||
@@ -152,6 +152,8 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// target loop
|
// target loop
|
||||||
while (sa.canAddMoreTarget()) {
|
while (sa.canAddMoreTarget()) {
|
||||||
|
list = CardLists.canSubsequentlyTarget(list, sa);
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if (!sa.isTargetNumberValid() || sa.getTargets().size() == 0) {
|
if (!sa.isTargetNumberValid() || sa.getTargets().size() == 0) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
final String damage = sa.getParam("NumDmg");
|
final String damage = sa.getParam("NumDmg");
|
||||||
int dmg = calculateDamageAmount(sa, source, damage);
|
int dmg = calculateDamageAmount(sa, source, damage);
|
||||||
|
|
||||||
if (damage.equals("X") || source.getSVar("X").equals("Count$xPaid")) {
|
if (damage.equals("X") || (dmg == 0 && source.getSVar("X").equals("Count$xPaid"))) {
|
||||||
if (sa.getSVar("X").equals("Count$xPaid") || sa.getSVar(damage).equals("Count$xPaid")) {
|
if (sa.getSVar("X").equals("Count$xPaid") || sa.getSVar(damage).equals("Count$xPaid")) {
|
||||||
dmg = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
dmg = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
|
||||||
|
|
||||||
|
|||||||
@@ -216,6 +216,8 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
CardCollection originalList = new CardCollection(list);
|
CardCollection originalList = new CardCollection(list);
|
||||||
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
|
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
|
||||||
|
|
||||||
|
list = CardLists.canSubsequentlyTarget(list, sa);
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
@@ -275,6 +277,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
choice = aura;
|
choice = aura;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO What about stolen permanents we're getting back at the end of the turn?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +287,9 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
list.remove(choice);
|
list.remove(choice);
|
||||||
sa.getTargets().add(choice);
|
if (sa.canTarget(choice)) {
|
||||||
|
sa.getTargets().add(choice);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (sa.hasParam("Defined")) {
|
} else if (sa.hasParam("Defined")) {
|
||||||
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||||
@@ -361,7 +366,10 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Card c = ComputerUtilCard.getBestAI(preferred);
|
Card c = ComputerUtilCard.getBestAI(preferred);
|
||||||
sa.getTargets().add(c);
|
|
||||||
|
if (sa.canTarget(c)) {
|
||||||
|
sa.getTargets().add(c);
|
||||||
|
}
|
||||||
preferred.remove(c);
|
preferred.remove(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,7 +390,9 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
} else {
|
} else {
|
||||||
c = ComputerUtilCard.getCheapestPermanentAI(list, sa, false);
|
c = ComputerUtilCard.getCheapestPermanentAI(list, sa, false);
|
||||||
}
|
}
|
||||||
sa.getTargets().add(c);
|
if (sa.canTarget(c)) {
|
||||||
|
sa.getTargets().add(c);
|
||||||
|
}
|
||||||
list.remove(c);
|
list.remove(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -515,12 +515,17 @@ public class DrawAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((computerHandSize + numCards > computerMaxHandSize)
|
if ((computerHandSize + numCards > computerMaxHandSize)) {
|
||||||
&& game.getPhaseHandler().isPlayerTurn(ai)
|
|
||||||
&& !sa.isTrigger()
|
|
||||||
&& !assumeSafeX) {
|
|
||||||
// Don't draw too many cards and then risk discarding cards at EOT
|
// Don't draw too many cards and then risk discarding cards at EOT
|
||||||
if (!drawback) {
|
if (game.getPhaseHandler().isPlayerTurn(ai)
|
||||||
|
&& !sa.isTrigger()
|
||||||
|
&& !assumeSafeX
|
||||||
|
&& !drawback) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (computerHandSize > computerMaxHandSize) {
|
||||||
|
// Don't make my hand size get too big if already at max
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
140
forge-ai/src/main/java/forge/ai/ability/EndureAi.java
Normal file
140
forge-ai/src/main/java/forge/ai/ability/EndureAi.java
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.SpellAbilityAi;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.card.*;
|
||||||
|
import forge.game.card.token.TokenInfo;
|
||||||
|
import forge.game.combat.Combat;
|
||||||
|
import forge.game.combat.CombatUtil;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class EndureAi extends SpellAbilityAi {
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
|
// Support for possible targeted Endure (e.g. target creature endures X)
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
|
Card bestCreature = ComputerUtilCard.getBestCreatureAI(aiPlayer.getCardsIn(ZoneType.Battlefield));
|
||||||
|
if (bestCreature == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(bestCreature);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean shouldPutCounters(Player ai, SpellAbility sa) {
|
||||||
|
// TODO: adapted from Fabricate AI in TokenAi, maybe can be refactored to a single method
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
final Game game = source.getGame();
|
||||||
|
final String num = sa.getParamOrDefault("Num", "1");
|
||||||
|
final int amount = AbilityUtils.calculateAmount(source, num, sa);
|
||||||
|
|
||||||
|
// if host would leave the play or if host is useless, create the token
|
||||||
|
if (source.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(ai, source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// need a copy for one with extra +1/+1 counter boost,
|
||||||
|
// without causing triggers to run
|
||||||
|
final Card copy = CardCopyService.getLKICopy(source);
|
||||||
|
copy.setCounters(CounterEnumType.P1P1, copy.getCounters(CounterEnumType.P1P1) + amount);
|
||||||
|
copy.setZone(source.getZone());
|
||||||
|
|
||||||
|
// if host would put into the battlefield attacking
|
||||||
|
Combat combat = source.getGame().getCombat();
|
||||||
|
if (combat != null && combat.isAttacking(source)) {
|
||||||
|
final Player defender = combat.getDefenderPlayerByAttacker(source);
|
||||||
|
return defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the host has haste and can attack
|
||||||
|
if (CombatUtil.canAttack(copy)) {
|
||||||
|
for (final Player opp : ai.getOpponents()) {
|
||||||
|
if (CombatUtil.canAttack(copy, opp) &&
|
||||||
|
opp.canLoseLife() &&
|
||||||
|
!ComputerUtilCard.canBeBlockedProfitably(opp, copy, true))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO check for trigger to turn token ETB into +1/+1 counter for host
|
||||||
|
// TODO check for trigger to turn token ETB into damage or life loss for opponent
|
||||||
|
// in these cases token might be preferred even if they would not survive
|
||||||
|
|
||||||
|
// evaluate creature with counters
|
||||||
|
int evalCounter = ComputerUtilCard.evaluateCreature(copy);
|
||||||
|
|
||||||
|
// spawn the token so it's possible to evaluate it
|
||||||
|
final Card token = TokenInfo.getProtoType("w_x_x_spirit", sa, ai, false);
|
||||||
|
|
||||||
|
token.setController(ai, 0);
|
||||||
|
token.setLastKnownZone(ai.getZone(ZoneType.Battlefield));
|
||||||
|
token.setTokenSpawningAbility(sa);
|
||||||
|
|
||||||
|
// evaluate the generated token
|
||||||
|
token.setBasePowerString(num);
|
||||||
|
token.setBasePower(amount);
|
||||||
|
token.setBaseToughnessString(num);
|
||||||
|
token.setBaseToughness(amount);
|
||||||
|
|
||||||
|
boolean result = true;
|
||||||
|
|
||||||
|
// need to check what the cards would be on the battlefield
|
||||||
|
// do not attach yet, that would cause Events
|
||||||
|
CardCollection preList = new CardCollection(token);
|
||||||
|
game.getAction().checkStaticAbilities(false, Sets.newHashSet(token), preList);
|
||||||
|
|
||||||
|
// token would not survive
|
||||||
|
if (!token.isCreature() || token.getNetToughness() < 1) {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
int evalToken = ComputerUtilCard.evaluateCreature(token);
|
||||||
|
result = evalToken < evalCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
//reset static abilities
|
||||||
|
game.getAction().checkStaticAbilities(false);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
|
||||||
|
return shouldPutCounters(player, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
// Support for possible targeted Endure (e.g. target creature endures X)
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
|
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield),
|
||||||
|
sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
|
||||||
|
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return canPlayAI(aiPlayer, sa) || mandatory;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ public class ManifestAi extends ManifestBaseAi {
|
|||||||
// (e.g. Grafdigger's Cage)
|
// (e.g. Grafdigger's Cage)
|
||||||
Card topCopy = CardCopyService.getLKICopy(card);
|
Card topCopy = CardCopyService.getLKICopy(card);
|
||||||
topCopy.turnFaceDownNoUpdate();
|
topCopy.turnFaceDownNoUpdate();
|
||||||
topCopy.setManifested(true);
|
topCopy.setManifested(sa);
|
||||||
|
|
||||||
if (ComputerUtil.isETBprevented(topCopy)) {
|
if (ComputerUtil.isETBprevented(topCopy)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import forge.game.phase.PhaseType;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
|
import forge.game.staticability.StaticAbilityMode;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -47,7 +48,7 @@ public class PermanentCreatureAi extends PermanentAi {
|
|||||||
if (sa.isDash()) {
|
if (sa.isDash()) {
|
||||||
//only checks that the dashed creature will attack
|
//only checks that the dashed creature will attack
|
||||||
if (ph.isPlayerTurn(ai) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
if (ph.isPlayerTurn(ai) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
if (game.getReplacementHandler().wouldPhaseBeSkipped(ai, "BeginCombat"))
|
if (game.getReplacementHandler().wouldPhaseBeSkipped(ai, PhaseType.COMBAT_BEGIN))
|
||||||
return false;
|
return false;
|
||||||
if (ComputerUtilCost.canPayCost(sa.getHostCard().getSpellPermanent(), ai, false)) {
|
if (ComputerUtilCost.canPayCost(sa.getHostCard().getSpellPermanent(), ai, false)) {
|
||||||
//do not dash if creature can be played normally
|
//do not dash if creature can be played normally
|
||||||
@@ -70,7 +71,7 @@ public class PermanentCreatureAi extends PermanentAi {
|
|||||||
// after attacking
|
// after attacking
|
||||||
if (card.hasSVar("EndOfTurnLeavePlay")
|
if (card.hasSVar("EndOfTurnLeavePlay")
|
||||||
&& (!ph.isPlayerTurn(ai) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
&& (!ph.isPlayerTurn(ai) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||||
|| game.getReplacementHandler().wouldPhaseBeSkipped(ai, "BeginCombat"))) {
|
|| game.getReplacementHandler().wouldPhaseBeSkipped(ai, PhaseType.COMBAT_BEGIN))) {
|
||||||
// AiPlayDecision.AnotherTime
|
// AiPlayDecision.AnotherTime
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -154,7 +155,7 @@ public class PermanentCreatureAi extends PermanentAi {
|
|||||||
boolean canCastAtOppTurn = true;
|
boolean canCastAtOppTurn = true;
|
||||||
for (Card c : ai.getGame().getCardsIn(ZoneType.Battlefield)) {
|
for (Card c : ai.getGame().getCardsIn(ZoneType.Battlefield)) {
|
||||||
for (StaticAbility s : c.getStaticAbilities()) {
|
for (StaticAbility s : c.getStaticAbilities()) {
|
||||||
if ("CantBeCast".equals(s.getParam("Mode")) && StringUtils.contains(s.getParam("Activator"), "NonActive")
|
if (s.checkMode(StaticAbilityMode.CantBeCast) && StringUtils.contains(s.getParam("Activator"), "NonActive")
|
||||||
&& (!s.getParam("Activator").startsWith("You") || c.getController().equals(ai))) {
|
&& (!s.getParam("Activator").startsWith("You") || c.getController().equals(ai))) {
|
||||||
canCastAtOppTurn = false;
|
canCastAtOppTurn = false;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import com.google.common.collect.Lists;
|
|||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import forge.ai.*;
|
import forge.ai.*;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityKey;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
@@ -15,9 +14,6 @@ import forge.game.phase.PhaseHandler;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerActionConfirmMode;
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
|
||||||
import forge.game.replacement.ReplacementLayer;
|
|
||||||
import forge.game.replacement.ReplacementType;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
@@ -61,6 +57,12 @@ public class PumpAi extends PumpAiBase {
|
|||||||
return SpecialAiLogic.doAristocratLogic(ai, sa);
|
return SpecialAiLogic.doAristocratLogic(ai, sa);
|
||||||
} else if (aiLogic.startsWith("AristocratCounters")) {
|
} else if (aiLogic.startsWith("AristocratCounters")) {
|
||||||
return SpecialAiLogic.doAristocratWithCountersLogic(ai, sa);
|
return SpecialAiLogic.doAristocratWithCountersLogic(ai, sa);
|
||||||
|
} else if (aiLogic.equals("SwitchPT")) {
|
||||||
|
// Some more AI would be even better, but this is a good start to prevent spamming
|
||||||
|
if (sa.isActivatedAbility() && sa.getActivationsThisTurn() > 0 && !sa.usesTargeting()) {
|
||||||
|
// Will prevent flipping back and forth
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.checkAiLogic(ai, sa, aiLogic);
|
return super.checkAiLogic(ai, sa, aiLogic);
|
||||||
@@ -81,6 +83,11 @@ public class PumpAi extends PumpAiBase {
|
|||||||
if (!ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && !isThreatened) {
|
if (!ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && !isThreatened) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (logic.equals("SwitchPT")) {
|
||||||
|
// Some more AI would be even better, but this is a good start to prevent spamming
|
||||||
|
if (ph.getPhase().isAfter(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE) || !ph.inCombat()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return super.checkPhaseRestrictions(ai, sa, ph);
|
return super.checkPhaseRestrictions(ai, sa, ph);
|
||||||
}
|
}
|
||||||
@@ -97,12 +104,6 @@ public class PumpAi extends PumpAiBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sa.hasParam("SwitchPT")) {
|
|
||||||
// Some more AI would be even better, but this is a good start to prevent spamming
|
|
||||||
if (ph.getPhase().isAfter(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE) || !ph.inCombat()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (game.getStack().isEmpty() && (ph.getPhase().isBefore(PhaseType.COMBAT_BEGIN)
|
if (game.getStack().isEmpty() && (ph.getPhase().isBefore(PhaseType.COMBAT_BEGIN)
|
||||||
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
|
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
|
||||||
// Instant-speed pumps should not be cast outside of combat when the
|
// Instant-speed pumps should not be cast outside of combat when the
|
||||||
@@ -247,14 +248,6 @@ public class PumpAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("SwitchPT")) {
|
|
||||||
// Some more AI would be even better, but this is a good start to prevent spamming
|
|
||||||
if (sa.isActivatedAbility() && sa.getActivationsThisTurn() > 0 && !sa.usesTargeting()) {
|
|
||||||
// Will prevent flipping back and forth
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -363,7 +356,7 @@ public class PumpAi extends PumpAiBase {
|
|||||||
} // pumpPlayAI()
|
} // pumpPlayAI()
|
||||||
|
|
||||||
private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defense, final int attack, final boolean mandatory,
|
private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defense, final int attack, final boolean mandatory,
|
||||||
boolean immediately) {
|
boolean immediately) {
|
||||||
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & "))
|
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & "))
|
||||||
: Lists.newArrayList();
|
: Lists.newArrayList();
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
@@ -473,65 +466,6 @@ public class PumpAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("SwitchPT")) {
|
|
||||||
// Logic to kill opponent creatures
|
|
||||||
CardCollection oppCreatures = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
|
|
||||||
if (!oppCreatures.isEmpty()) {
|
|
||||||
CardCollection oppCreaturesFiltered = CardLists.filter(oppCreatures, card -> {
|
|
||||||
// don't care about useless creatures
|
|
||||||
if (ComputerUtilCard.isUselessCreature(ai, card)
|
|
||||||
|| card.hasSVar("EndOfTurnLeavePlay")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// dies by target
|
|
||||||
if (card.getSVar("Targeting").equals("Dies")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// dies by state based action
|
|
||||||
if (card.getNetPower() <= 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (card.hasKeyword(Keyword.INDESTRUCTIBLE)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// check if switching PT causes it to be lethal
|
|
||||||
Card lki = CardCopyService.getLKICopy(card);
|
|
||||||
lki.addSwitchPT(-1, 0);
|
|
||||||
|
|
||||||
// check if creature could regenerate
|
|
||||||
Map<AbilityKey, Object> runParams = AbilityKey.mapFromAffected(card);
|
|
||||||
runParams.put(AbilityKey.Regeneration, true);
|
|
||||||
List<ReplacementEffect> repDestoryList = game.getReplacementHandler().getReplacementList(ReplacementType.Destroy, runParams, ReplacementLayer.Other);
|
|
||||||
// non-Regeneration one like Totem-Armor
|
|
||||||
// should do it anyway to destroy the aura?
|
|
||||||
if (repDestoryList.stream().anyMatch(r -> !r.hasParam("Regeneration"))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// TODO make it force to use regen?
|
|
||||||
// should check phase and make it before combat damage or better before blocker?
|
|
||||||
if (repDestoryList.stream().anyMatch(r -> r.hasParam("Regeneration")) && card.canBeShielded()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// maybe do it anyway to reduce its power?
|
|
||||||
if (card.getLethal() - card.getDamage() > 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
// the ones that die by switching PT
|
|
||||||
if (!oppCreaturesFiltered.isEmpty()) {
|
|
||||||
Card best = ComputerUtilCard.getBestCreatureAI(oppCreaturesFiltered);
|
|
||||||
if (best != null) {
|
|
||||||
sa.getTargets().add(best);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
for (final Player opp : ai.getOpponents()) {
|
for (final Player opp : ai.getOpponents()) {
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
@@ -608,6 +542,8 @@ public class PumpAi extends PumpAiBase {
|
|||||||
Card t = null;
|
Card t = null;
|
||||||
// boolean goodt = false;
|
// boolean goodt = false;
|
||||||
|
|
||||||
|
list = CardLists.canSubsequentlyTarget(list, sa);
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
if (mandatory || ComputerUtil.activateForCost(sa, ai)) {
|
if (mandatory || ComputerUtil.activateForCost(sa, ai)) {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import forge.game.combat.CombatUtil;
|
|||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.phase.Untap;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
@@ -137,7 +136,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
return CombatUtil.canBlockAtLeastOne(card, attackers);
|
return CombatUtil.canBlockAtLeastOne(card, attackers);
|
||||||
} else if (keyword.endsWith("This card doesn't untap during your next untap step.")) {
|
} else if (keyword.endsWith("This card doesn't untap during your next untap step.")) {
|
||||||
return !ph.getPhase().isBefore(PhaseType.MAIN2) && !card.isUntapped() && ph.isPlayerTurn(ai)
|
return !ph.getPhase().isBefore(PhaseType.MAIN2) && !card.isUntapped() && ph.isPlayerTurn(ai)
|
||||||
&& Untap.canUntap(card);
|
&& card.canUntap(card.getController(), true);
|
||||||
} else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.")
|
} else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.")
|
||||||
|| keyword.endsWith("Prevent all damage that would be dealt by CARDNAME.")) {
|
|| keyword.endsWith("Prevent all damage that would be dealt by CARDNAME.")) {
|
||||||
if (ph.isPlayerTurn(ai) && (!(CombatUtil.canBlock(card) || combat != null && combat.isBlocking(card))
|
if (ph.isPlayerTurn(ai) && (!(CombatUtil.canBlock(card) || combat != null && combat.isBlocking(card))
|
||||||
|
|||||||
@@ -367,8 +367,7 @@ public class TokenAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean tgtRoleAura(final Player ai, final SpellAbility sa, final Card tok, final boolean mandatory) {
|
private boolean tgtRoleAura(final Player ai, final SpellAbility sa, final Card tok, final boolean mandatory) {
|
||||||
boolean isCurse = "Curse".equals(sa.getParam("AILogic")) ||
|
boolean isCurse = "Curse".equals(sa.getParam("AILogic")) || "Curse".equals(tok.getSVar("AttachAILogic"));
|
||||||
tok.getFirstAttachSpell().getParamOrDefault("AILogic", "").equals("Curse");
|
|
||||||
List<Card> tgts = CardUtil.getValidCardsToTarget(sa);
|
List<Card> tgts = CardUtil.getValidCardsToTarget(sa);
|
||||||
|
|
||||||
// look for card without role from ai
|
// look for card without role from ai
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import forge.game.cost.CostTap;
|
|||||||
import forge.game.mana.ManaCostBeingPaid;
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.phase.Untap;
|
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerCollection;
|
import forge.game.player.PlayerCollection;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -338,7 +337,7 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// See if there's anything to untap that is tapped and that doesn't untap during the next untap step by itself
|
// See if there's anything to untap that is tapped and that doesn't untap during the next untap step by itself
|
||||||
CardCollection noAutoUntap = CardLists.filter(untapList, Untap.CANUNTAP.negate());
|
CardCollection noAutoUntap = CardLists.filter(untapList, c -> !c.canUntap(c.getController(), true));
|
||||||
if (!noAutoUntap.isEmpty()) {
|
if (!noAutoUntap.isEmpty()) {
|
||||||
return ComputerUtilCard.getBestAI(noAutoUntap);
|
return ComputerUtilCard.getBestAI(noAutoUntap);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ public class GameCopier {
|
|||||||
newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn());
|
newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn());
|
||||||
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
|
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
|
||||||
newPlayer.setSpeed(origPlayer.getSpeed());
|
newPlayer.setSpeed(origPlayer.getSpeed());
|
||||||
newPlayer.setBlessing(origPlayer.hasBlessing());
|
newPlayer.setBlessing(origPlayer.hasBlessing(), null);
|
||||||
newPlayer.setRevolt(origPlayer.hasRevolt());
|
newPlayer.setRevolt(origPlayer.hasRevolt());
|
||||||
newPlayer.setDescended(origPlayer.getDescended());
|
newPlayer.setDescended(origPlayer.getDescended());
|
||||||
newPlayer.setLibrarySearched(origPlayer.getLibrarySearched());
|
newPlayer.setLibrarySearched(origPlayer.getLibrarySearched());
|
||||||
@@ -356,7 +356,6 @@ public class GameCopier {
|
|||||||
newCard.setPTCharacterDefiningTable(c.getSetPTCharacterDefiningTable());
|
newCard.setPTCharacterDefiningTable(c.getSetPTCharacterDefiningTable());
|
||||||
|
|
||||||
newCard.setPTBoost(c.getPTBoostTable());
|
newCard.setPTBoost(c.getPTBoostTable());
|
||||||
newCard.setSwitchPTTable(c.getSwitchPTTable());
|
|
||||||
// TODO copy by map
|
// TODO copy by map
|
||||||
newCard.setDamage(c.getDamage());
|
newCard.setDamage(c.getDamage());
|
||||||
newCard.setDamageReceivedThisTurn(c.getDamageReceivedThisTurn());
|
newCard.setDamageReceivedThisTurn(c.getDamageReceivedThisTurn());
|
||||||
@@ -374,10 +373,10 @@ public class GameCopier {
|
|||||||
if (c.isFaceDown()) {
|
if (c.isFaceDown()) {
|
||||||
newCard.turnFaceDown(true);
|
newCard.turnFaceDown(true);
|
||||||
if (c.isManifested()) {
|
if (c.isManifested()) {
|
||||||
newCard.setManifested(true);
|
newCard.setManifested(c.getManifestedSA());
|
||||||
}
|
}
|
||||||
if (c.isCloaked()) {
|
if (c.isCloaked()) {
|
||||||
newCard.setCloaked(true);
|
newCard.setCloaked(c.getCloakedSA());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (c.isMonstrous()) {
|
if (c.isMonstrous()) {
|
||||||
|
|||||||
@@ -23,17 +23,16 @@ public final class ImageKeys {
|
|||||||
|
|
||||||
public static final String HIDDEN_CARD = "hidden";
|
public static final String HIDDEN_CARD = "hidden";
|
||||||
public static final String MORPH_IMAGE = "morph";
|
public static final String MORPH_IMAGE = "morph";
|
||||||
public static final String DISGUISED_IMAGE = "disguised";
|
|
||||||
public static final String MANIFEST_IMAGE = "manifest";
|
public static final String MANIFEST_IMAGE = "manifest";
|
||||||
public static final String CLOAKED_IMAGE = "cloaked";
|
public static final String CLOAKED_IMAGE = "cloaked";
|
||||||
public static final String FORETELL_IMAGE = "foretell";
|
public static final String FORETELL_IMAGE = "foretell";
|
||||||
|
public static final String BLESSING_IMAGE = "blessing";
|
||||||
|
public static final String INITIATIVE_IMAGE = "initiative";
|
||||||
|
public static final String MONARCH_IMAGE = "monarch";
|
||||||
|
public static final String THE_RING_IMAGE = "the_ring";
|
||||||
|
public static final String RADIATION_IMAGE = "radiation";
|
||||||
|
|
||||||
public static final String BACKFACE_POSTFIX = "$alt";
|
public static final String BACKFACE_POSTFIX = "$alt";
|
||||||
public static final String SPECFACE_W = "$wspec";
|
|
||||||
public static final String SPECFACE_U = "$uspec";
|
|
||||||
public static final String SPECFACE_B = "$bspec";
|
|
||||||
public static final String SPECFACE_R = "$rspec";
|
|
||||||
public static final String SPECFACE_G = "$gspec";
|
|
||||||
|
|
||||||
private static String CACHE_CARD_PICS_DIR, CACHE_TOKEN_PICS_DIR, CACHE_ICON_PICS_DIR, CACHE_BOOSTER_PICS_DIR,
|
private static String CACHE_CARD_PICS_DIR, CACHE_TOKEN_PICS_DIR, CACHE_ICON_PICS_DIR, CACHE_BOOSTER_PICS_DIR,
|
||||||
CACHE_FATPACK_PICS_DIR, CACHE_BOOSTERBOX_PICS_DIR, CACHE_PRECON_PICS_DIR, CACHE_TOURNAMENTPACK_PICS_DIR;
|
CACHE_FATPACK_PICS_DIR, CACHE_BOOSTERBOX_PICS_DIR, CACHE_PRECON_PICS_DIR, CACHE_TOURNAMENTPACK_PICS_DIR;
|
||||||
@@ -93,13 +92,38 @@ public final class ImageKeys {
|
|||||||
return cachedCards.get(key);
|
return cachedCards.get(key);
|
||||||
}
|
}
|
||||||
public static File getImageFile(String key) {
|
public static File getImageFile(String key) {
|
||||||
|
return getImageFile(key, false);
|
||||||
|
}
|
||||||
|
public static File getImageFile(String key, boolean artCrop) {
|
||||||
if (StringUtils.isEmpty(key))
|
if (StringUtils.isEmpty(key))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
final String dir;
|
final String dir;
|
||||||
final String filename;
|
final String filename;
|
||||||
if (key.startsWith(ImageKeys.TOKEN_PREFIX)) {
|
String[] tempdata = null;
|
||||||
filename = key.substring(ImageKeys.TOKEN_PREFIX.length());
|
if (key.startsWith(ImageKeys.CARD_PREFIX)) {
|
||||||
|
tempdata = key.substring(ImageKeys.CARD_PREFIX.length()).split("\\|");
|
||||||
|
String tokenname = tempdata[0];
|
||||||
|
if (tempdata.length > 1) {
|
||||||
|
tokenname += "_" + tempdata[1];
|
||||||
|
}
|
||||||
|
if (tempdata.length > 2) {
|
||||||
|
tokenname += "_" + tempdata[2];
|
||||||
|
}
|
||||||
|
filename = tokenname ;
|
||||||
|
|
||||||
|
dir = CACHE_CARD_PICS_DIR;
|
||||||
|
} else if (key.startsWith(ImageKeys.TOKEN_PREFIX)) {
|
||||||
|
tempdata = key.substring(ImageKeys.TOKEN_PREFIX.length()).split("\\|");
|
||||||
|
String tokenname = tempdata[0];
|
||||||
|
if (tempdata.length > 1) {
|
||||||
|
tokenname += "_" + tempdata[1];
|
||||||
|
}
|
||||||
|
if (tempdata.length > 2) {
|
||||||
|
tokenname += "_" + tempdata[2];
|
||||||
|
}
|
||||||
|
filename = tokenname;
|
||||||
|
|
||||||
dir = CACHE_TOKEN_PICS_DIR;
|
dir = CACHE_TOKEN_PICS_DIR;
|
||||||
} else if (key.startsWith(ImageKeys.ICON_PREFIX)) {
|
} else if (key.startsWith(ImageKeys.ICON_PREFIX)) {
|
||||||
filename = key.substring(ImageKeys.ICON_PREFIX.length());
|
filename = key.substring(ImageKeys.ICON_PREFIX.length());
|
||||||
@@ -140,6 +164,54 @@ public final class ImageKeys {
|
|||||||
cachedCards.put(filename, file);
|
cachedCards.put(filename, file);
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
if (tempdata != null && dir.equals(CACHE_CARD_PICS_DIR)) {
|
||||||
|
String setlessFilename = tempdata[0] + (artCrop ? ".artcrop" : ".fullborder");
|
||||||
|
String setCode = tempdata.length > 1 ? tempdata[1] : "";
|
||||||
|
String collectorNumber = tempdata.length > 2 ? tempdata[2] : "";
|
||||||
|
if (!setCode.isEmpty()) {
|
||||||
|
if (!collectorNumber.isEmpty()) {
|
||||||
|
file = findFile(dir, setCode + "/" + collectorNumber + "_" + setlessFilename);
|
||||||
|
if (file != null) {
|
||||||
|
cachedCards.put(filename, file);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file = findFile(dir, setCode + "/" + setlessFilename);
|
||||||
|
if (file != null) {
|
||||||
|
cachedCards.put(filename, file);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file = findFile(dir, setlessFilename);
|
||||||
|
if (file != null) {
|
||||||
|
cachedCards.put(filename, file);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tempdata != null && dir.equals(CACHE_TOKEN_PICS_DIR)) {
|
||||||
|
String setlessFilename = tempdata[0];
|
||||||
|
String setCode = tempdata.length > 1 ? tempdata[1] : "";
|
||||||
|
String collectorNumber = tempdata.length > 2 ? tempdata[2] : "";
|
||||||
|
if (!setCode.isEmpty()) {
|
||||||
|
if (!collectorNumber.isEmpty()) {
|
||||||
|
file = findFile(dir, setCode + "/" + collectorNumber + "_" + setlessFilename);
|
||||||
|
if (file != null) {
|
||||||
|
cachedCards.put(filename, file);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file = findFile(dir, setCode + "/" + setlessFilename);
|
||||||
|
if (file != null) {
|
||||||
|
cachedCards.put(filename, file);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file = findFile(dir, setlessFilename);
|
||||||
|
if (file != null) {
|
||||||
|
cachedCards.put(filename, file);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AE -> Ae and Ae -> AE for older cards with different file names
|
// AE -> Ae and Ae -> AE for older cards with different file names
|
||||||
// on case-sensitive file systems
|
// on case-sensitive file systems
|
||||||
@@ -221,39 +293,7 @@ public final class ImageKeys {
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dir.equals(CACHE_TOKEN_PICS_DIR)) {
|
if (filename.contains("/")) {
|
||||||
int index = filename.lastIndexOf('_');
|
|
||||||
if (index != -1) {
|
|
||||||
String setlessFilename = filename.substring(0, index);
|
|
||||||
String setCode = filename.substring(index + 1);
|
|
||||||
// try with upper case set
|
|
||||||
file = findFile(dir, setlessFilename + "_" + setCode.toUpperCase());
|
|
||||||
if (file != null) {
|
|
||||||
cachedCards.put(filename, file);
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
// try with lower case set
|
|
||||||
file = findFile(dir, setlessFilename + "_" + setCode.toLowerCase());
|
|
||||||
if (file != null) {
|
|
||||||
cachedCards.put(filename, file);
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
// try without set name
|
|
||||||
file = findFile(dir, setlessFilename);
|
|
||||||
if (file != null) {
|
|
||||||
cachedCards.put(filename, file);
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
// if there's an art variant try without it
|
|
||||||
if (setlessFilename.matches(".*[0-9]*$")) {
|
|
||||||
file = findFile(dir, setlessFilename.replaceAll("[0-9]*$", ""));
|
|
||||||
if (file != null) {
|
|
||||||
cachedCards.put(filename, file);
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (filename.contains("/")) {
|
|
||||||
String setlessFilename = filename.substring(filename.indexOf('/') + 1);
|
String setlessFilename = filename.substring(filename.indexOf('/') + 1);
|
||||||
file = findFile(dir, setlessFilename);
|
file = findFile(dir, setlessFilename);
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
|
|||||||
@@ -95,12 +95,12 @@ public class StaticData {
|
|||||||
if (!loadNonLegalCards) {
|
if (!loadNonLegalCards) {
|
||||||
for (CardEdition e : editions) {
|
for (CardEdition e : editions) {
|
||||||
if (e.getType() == CardEdition.Type.FUNNY || e.getBorderColor() == CardEdition.BorderColor.SILVER) {
|
if (e.getType() == CardEdition.Type.FUNNY || e.getBorderColor() == CardEdition.BorderColor.SILVER) {
|
||||||
List<CardEdition.CardInSet> eternalCards = e.getFunnyEternalCards();
|
List<CardEdition.EditionEntry> eternalCards = e.getFunnyEternalCards();
|
||||||
|
|
||||||
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
|
for (CardEdition.EditionEntry cis : e.getAllCardsInSet()) {
|
||||||
if (eternalCards.contains(cis))
|
if (eternalCards.contains(cis))
|
||||||
continue;
|
continue;
|
||||||
funnyCards.add(cis.name);
|
funnyCards.add(cis.name());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,6 +217,9 @@ public class StaticData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CardEdition getCardEdition(String setCode) {
|
public CardEdition getCardEdition(String setCode) {
|
||||||
|
if (CardEdition.UNKNOWN_CODE.equals(setCode)) {
|
||||||
|
return CardEdition.UNKNOWN;
|
||||||
|
}
|
||||||
CardEdition edition = this.editions.get(setCode);
|
CardEdition edition = this.editions.get(setCode);
|
||||||
return edition;
|
return edition;
|
||||||
}
|
}
|
||||||
@@ -248,6 +251,15 @@ public class StaticData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a PaperCard by looking at all available card databases for any matching print.
|
||||||
|
* @param cardName The name of the card
|
||||||
|
* @return PaperCard instance found in one of the available CardDb databases, or <code>null</code> if not found.
|
||||||
|
*/
|
||||||
|
public PaperCard fetchCard(final String cardName) {
|
||||||
|
return fetchCard(cardName, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a PaperCard by looking at all available card databases;
|
* Retrieve a PaperCard by looking at all available card databases;
|
||||||
* @param cardName The name of the card
|
* @param cardName The name of the card
|
||||||
@@ -778,11 +790,11 @@ public class StaticData {
|
|||||||
|
|
||||||
Map<String, Pair<Boolean, Integer>> cardCount = new HashMap<>();
|
Map<String, Pair<Boolean, Integer>> cardCount = new HashMap<>();
|
||||||
List<CompletableFuture<?>> futures = new ArrayList<>();
|
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||||
for (CardEdition.CardInSet c : e.getAllCardsInSet()) {
|
for (CardEdition.EditionEntry c : e.getAllCardsInSet()) {
|
||||||
if (cardCount.containsKey(c.name)) {
|
if (cardCount.containsKey(c.name())) {
|
||||||
cardCount.put(c.name, Pair.of(c.collectorNumber != null && c.collectorNumber.startsWith("F"), cardCount.get(c.name).getRight() + 1));
|
cardCount.put(c.name(), Pair.of(c.collectorNumber() != null && c.collectorNumber().startsWith("F"), cardCount.get(c.name()).getRight() + 1));
|
||||||
} else {
|
} else {
|
||||||
cardCount.put(c.name, Pair.of(c.collectorNumber != null && c.collectorNumber.startsWith("F"), 1));
|
cardCount.put(c.name(), Pair.of(c.collectorNumber() != null && c.collectorNumber().startsWith("F"), 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -844,9 +856,9 @@ public class StaticData {
|
|||||||
futures.clear();
|
futures.clear();
|
||||||
|
|
||||||
// TODO: Audit token images here...
|
// TODO: Audit token images here...
|
||||||
for(Map.Entry<String, Integer> tokenEntry : e.getTokens().entrySet()) {
|
for(Map.Entry<String, Collection<CardEdition.EditionEntry>> tokenEntry : e.getTokens().asMap().entrySet()) {
|
||||||
final String name = tokenEntry.getKey();
|
final String name = tokenEntry.getKey();
|
||||||
final int artIndex = tokenEntry.getValue();
|
final int artIndex = tokenEntry.getValue().size();
|
||||||
try {
|
try {
|
||||||
PaperToken token = getAllTokens().getToken(name, e.getCode());
|
PaperToken token = getAllTokens().getToken(name, e.getCode());
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
@@ -983,4 +995,23 @@ public class StaticData {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
public String getOtherImageKey(String name, String set) {
|
||||||
|
if (this.editions.get(set) != null) {
|
||||||
|
String realSetCode = this.editions.get(set).getOtherSet(name);
|
||||||
|
if (realSetCode != null) {
|
||||||
|
CardEdition.EditionEntry ee = this.editions.get(realSetCode).findOther(name);
|
||||||
|
if (ee != null) { // TODO add collector Number and new ImageKey format
|
||||||
|
return ImageKeys.getTokenKey(String.format("%s|%s|%s", name, realSetCode, ee.collectorNumber()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (CardEdition e : this.editions) {
|
||||||
|
CardEdition.EditionEntry ee = e.findOther(name);
|
||||||
|
if (ee != null) { // TODO add collector Number and new ImageKey format
|
||||||
|
return ImageKeys.getTokenKey(String.format("%s|%s|%s", name, e.getCode(), ee.collectorNumber()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// final fallback
|
||||||
|
return ImageKeys.getTokenKey(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import com.google.common.collect.Lists;
|
|||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Multimaps;
|
import com.google.common.collect.Multimaps;
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
import forge.card.CardEdition.CardInSet;
|
import forge.card.CardEdition.EditionEntry;
|
||||||
import forge.card.CardEdition.Type;
|
import forge.card.CardEdition.Type;
|
||||||
import forge.deck.generation.IDeckGenPool;
|
import forge.deck.generation.IDeckGenPool;
|
||||||
import forge.item.IPaperCard;
|
import forge.item.IPaperCard;
|
||||||
@@ -42,7 +42,8 @@ import java.util.stream.Stream;
|
|||||||
public final class CardDb implements ICardDatabase, IDeckGenPool {
|
public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||||
public final static String foilSuffix = "+";
|
public final static String foilSuffix = "+";
|
||||||
public final static char NameSetSeparator = '|';
|
public final static char NameSetSeparator = '|';
|
||||||
public final static String colorIDPrefix = "#";
|
public final static String FlagPrefix = "#";
|
||||||
|
public static final String FlagSeparator = "\t";
|
||||||
private final String exlcudedCardName = "Concentrate";
|
private final String exlcudedCardName = "Concentrate";
|
||||||
private final String exlcudedCardSet = "DS0";
|
private final String exlcudedCardSet = "DS0";
|
||||||
|
|
||||||
@@ -93,19 +94,19 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
public int artIndex;
|
public int artIndex;
|
||||||
public boolean isFoil;
|
public boolean isFoil;
|
||||||
public String collectorNumber;
|
public String collectorNumber;
|
||||||
public Set<String> colorID;
|
public Map<String, String> flags;
|
||||||
|
|
||||||
private CardRequest(String name, String edition, int artIndex, boolean isFoil, String collectorNumber) {
|
private CardRequest(String name, String edition, int artIndex, boolean isFoil, String collectorNumber) {
|
||||||
this(name, edition, artIndex, isFoil, collectorNumber, null);
|
this(name, edition, artIndex, isFoil, collectorNumber, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CardRequest(String name, String edition, int artIndex, boolean isFoil, String collectorNumber, Set<String> colorID) {
|
private CardRequest(String name, String edition, int artIndex, boolean isFoil, String collectorNumber, Map<String, String> flags) {
|
||||||
cardName = name;
|
cardName = name;
|
||||||
this.edition = edition;
|
this.edition = edition;
|
||||||
this.artIndex = artIndex;
|
this.artIndex = artIndex;
|
||||||
this.isFoil = isFoil;
|
this.isFoil = isFoil;
|
||||||
this.collectorNumber = collectorNumber;
|
this.collectorNumber = collectorNumber;
|
||||||
this.colorID = colorID;
|
this.flags = flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isFoilCardName(final String cardName){
|
public static boolean isFoilCardName(final String cardName){
|
||||||
@@ -120,7 +121,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String compose(String cardName, String setCode) {
|
public static String compose(String cardName, String setCode) {
|
||||||
setCode = setCode != null ? setCode : "";
|
if(setCode == null || StringUtils.isBlank(setCode) || setCode.equals(CardEdition.UNKNOWN_CODE))
|
||||||
|
setCode = "";
|
||||||
cardName = cardName != null ? cardName : "";
|
cardName = cardName != null ? cardName : "";
|
||||||
if (cardName.indexOf(NameSetSeparator) != -1)
|
if (cardName.indexOf(NameSetSeparator) != -1)
|
||||||
// If cardName is another RequestString, just get card name and forget about the rest.
|
// If cardName is another RequestString, just get card name and forget about the rest.
|
||||||
@@ -134,14 +136,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
return requestInfo + NameSetSeparator + artIndex;
|
return requestInfo + NameSetSeparator + artIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String compose(String cardName, String setCode, int artIndex, Set<String> colorID) {
|
|
||||||
String requestInfo = compose(cardName, setCode);
|
|
||||||
artIndex = Math.max(artIndex, IPaperCard.DEFAULT_ART_INDEX);
|
|
||||||
String cid = colorID == null ? "" : NameSetSeparator +
|
|
||||||
colorID.toString().replace("[", colorIDPrefix).replace(", ", colorIDPrefix).replace("]", "");
|
|
||||||
return requestInfo + NameSetSeparator + artIndex + cid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String compose(String cardName, String setCode, String collectorNumber) {
|
public static String compose(String cardName, String setCode, String collectorNumber) {
|
||||||
String requestInfo = compose(cardName, setCode);
|
String requestInfo = compose(cardName, setCode);
|
||||||
// CollectorNumber will be wrapped in square brackets
|
// CollectorNumber will be wrapped in square brackets
|
||||||
@@ -149,6 +143,34 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
return requestInfo + NameSetSeparator + collectorNumber;
|
return requestInfo + NameSetSeparator + collectorNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String compose(String cardName, String setCode, int artIndex, Map<String, String> flags) {
|
||||||
|
String requestInfo = compose(cardName, setCode);
|
||||||
|
artIndex = Math.max(artIndex, IPaperCard.DEFAULT_ART_INDEX);
|
||||||
|
if(flags == null)
|
||||||
|
return requestInfo + NameSetSeparator + artIndex;
|
||||||
|
return requestInfo + NameSetSeparator + artIndex + getFlagSegment(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String compose(String cardName, String setCode, String collectorNumber, Map<String, String> flags) {
|
||||||
|
String requestInfo = compose(cardName, setCode);
|
||||||
|
collectorNumber = preprocessCollectorNumber(collectorNumber);
|
||||||
|
if(flags == null || flags.isEmpty())
|
||||||
|
return requestInfo + NameSetSeparator + collectorNumber;
|
||||||
|
return requestInfo + NameSetSeparator + collectorNumber + getFlagSegment(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String compose(PaperCard card) {
|
||||||
|
String name = compose(card.getName(), card.isFoil());
|
||||||
|
return compose(name, card.getEdition(), card.getCollectorNumber(), card.getMarkedFlags().toMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String compose(String cardName, String setCode, int artIndex, String collectorNumber) {
|
||||||
|
String requestInfo = compose(cardName, setCode, artIndex);
|
||||||
|
// CollectorNumber will be wrapped in square brackets
|
||||||
|
collectorNumber = preprocessCollectorNumber(collectorNumber);
|
||||||
|
return requestInfo + NameSetSeparator + collectorNumber;
|
||||||
|
}
|
||||||
|
|
||||||
private static String preprocessCollectorNumber(String collectorNumber) {
|
private static String preprocessCollectorNumber(String collectorNumber) {
|
||||||
if (collectorNumber == null)
|
if (collectorNumber == null)
|
||||||
return "";
|
return "";
|
||||||
@@ -160,19 +182,21 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
return collectorNumber;
|
return collectorNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String compose(String cardName, String setCode, int artIndex, String collectorNumber) {
|
private static String getFlagSegment(Map<String, String> flags) {
|
||||||
String requestInfo = compose(cardName, setCode, artIndex);
|
if(flags == null)
|
||||||
// CollectorNumber will be wrapped in square brackets
|
return "";
|
||||||
collectorNumber = preprocessCollectorNumber(collectorNumber);
|
String flagText = flags.entrySet().stream()
|
||||||
return requestInfo + NameSetSeparator + collectorNumber;
|
.map(e -> e.getKey() + "=" + e.getValue())
|
||||||
|
.collect(Collectors.joining(FlagSeparator));
|
||||||
|
return NameSetSeparator + FlagPrefix + "{" + flagText + "}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isCollectorNumber(String s) {
|
private static boolean isCollectorNumber(String s) {
|
||||||
return s.startsWith("[") && s.endsWith("]");
|
return s.startsWith("[") && s.endsWith("]");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isColorIDString(String s) {
|
private static boolean isFlagSegment(String s) {
|
||||||
return s.startsWith(colorIDPrefix);
|
return s.startsWith(FlagPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isArtIndex(String s) {
|
private static boolean isArtIndex(String s) {
|
||||||
@@ -201,44 +225,36 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
String[] info = TextUtil.split(reqInfo, NameSetSeparator);
|
String[] info = TextUtil.split(reqInfo, NameSetSeparator);
|
||||||
int setPos;
|
int index = 1;
|
||||||
int artPos;
|
|
||||||
int cNrPos;
|
|
||||||
int clrPos;
|
|
||||||
if (info.length >= 4) { // name|set|artIndex|[collNr]
|
|
||||||
setPos = isSetCode(info[1]) ? 1 : -1;
|
|
||||||
artPos = isArtIndex(info[2]) ? 2 : -1;
|
|
||||||
cNrPos = isCollectorNumber(info[3]) ? 3 : -1;
|
|
||||||
int pos = cNrPos > 0 ? -1 : 3;
|
|
||||||
clrPos = pos > 0 ? isColorIDString(info[pos]) ? pos : -1 : -1;
|
|
||||||
} else if (info.length == 3) { // name|set|artIndex (or CollNr)
|
|
||||||
setPos = isSetCode(info[1]) ? 1 : -1;
|
|
||||||
artPos = isArtIndex(info[2]) ? 2 : -1;
|
|
||||||
cNrPos = isCollectorNumber(info[2]) ? 2 : -1;
|
|
||||||
int pos = cNrPos > 0 ? -1 : 2;
|
|
||||||
clrPos = pos > 0 ? isColorIDString(info[pos]) ? pos : -1 : -1;
|
|
||||||
} else if (info.length == 2) { // name|set (or artIndex, even if not possible via compose)
|
|
||||||
setPos = isSetCode(info[1]) ? 1 : -1;
|
|
||||||
artPos = isArtIndex(info[1]) ? 1 : -1;
|
|
||||||
cNrPos = -1;
|
|
||||||
clrPos = -1;
|
|
||||||
} else {
|
|
||||||
setPos = -1;
|
|
||||||
artPos = -1;
|
|
||||||
cNrPos = -1;
|
|
||||||
clrPos = -1;
|
|
||||||
}
|
|
||||||
String cardName = info[0];
|
String cardName = info[0];
|
||||||
boolean isFoil = false;
|
boolean isFoil = false;
|
||||||
|
int artIndex = IPaperCard.NO_ART_INDEX;
|
||||||
|
String setCode = null;
|
||||||
|
String collectorNumber = IPaperCard.NO_COLLECTOR_NUMBER;
|
||||||
|
Map<String, String> flags = null;
|
||||||
if (isFoilCardName(cardName)) {
|
if (isFoilCardName(cardName)) {
|
||||||
cardName = cardName.substring(0, cardName.length() - foilSuffix.length());
|
cardName = cardName.substring(0, cardName.length() - foilSuffix.length());
|
||||||
isFoil = true;
|
isFoil = true;
|
||||||
}
|
}
|
||||||
int artIndex = artPos > 0 ? Integer.parseInt(info[artPos]) : IPaperCard.NO_ART_INDEX; // default: no art index
|
|
||||||
String collectorNumber = cNrPos > 0 ? info[cNrPos].substring(1, info[cNrPos].length() - 1) : IPaperCard.NO_COLLECTOR_NUMBER;
|
if(info.length > index && isSetCode(info[index])) {
|
||||||
String setCode = setPos > 0 ? info[setPos] : null;
|
setCode = info[index];
|
||||||
Set<String> colorID = clrPos > 0 ? Arrays.stream(info[clrPos].substring(1).split(colorIDPrefix)).collect(Collectors.toSet()) : null;
|
index++;
|
||||||
if (setCode != null && setCode.equals(CardEdition.UNKNOWN.getCode())) { // ???
|
}
|
||||||
|
if(info.length > index && isArtIndex(info[index])) {
|
||||||
|
artIndex = Integer.parseInt(info[index]);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
if(info.length > index && isCollectorNumber(info[index])) {
|
||||||
|
collectorNumber = info[index].substring(1, info[index].length() - 1);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
if (info.length > index && isFlagSegment(info[index])) {
|
||||||
|
String flagText = info[index].substring(FlagPrefix.length());
|
||||||
|
flags = parseRequestFlags(flagText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CardEdition.UNKNOWN_CODE.equals(setCode)) { // ???
|
||||||
setCode = null;
|
setCode = null;
|
||||||
}
|
}
|
||||||
if (setCode == null) {
|
if (setCode == null) {
|
||||||
@@ -253,7 +269,29 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
// finally, check whether any between artIndex and CollectorNumber has been set
|
// finally, check whether any between artIndex and CollectorNumber has been set
|
||||||
if (collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER) && artIndex == IPaperCard.NO_ART_INDEX)
|
if (collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER) && artIndex == IPaperCard.NO_ART_INDEX)
|
||||||
artIndex = IPaperCard.DEFAULT_ART_INDEX;
|
artIndex = IPaperCard.DEFAULT_ART_INDEX;
|
||||||
return new CardRequest(cardName, setCode, artIndex, isFoil, collectorNumber, colorID);
|
return new CardRequest(cardName, setCode, artIndex, isFoil, collectorNumber, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, String> parseRequestFlags(String flagText) {
|
||||||
|
flagText = flagText.trim();
|
||||||
|
if(flagText.isEmpty())
|
||||||
|
return null;
|
||||||
|
if(!flagText.startsWith("{")) {
|
||||||
|
//Legacy form for marked colors. They'll be of the form "W#B#R"
|
||||||
|
Map<String, String> flags = new HashMap<>();
|
||||||
|
String normalizedColorString = ColorSet.fromNames(flagText.split(FlagPrefix)).toString();
|
||||||
|
flags.put("markedColors", String.join("", normalizedColorString));
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
flagText = flagText.substring(1, flagText.length() - 1); //Trim the braces.
|
||||||
|
//List of flags, a series of "key=value" text broken up by tabs.
|
||||||
|
return Arrays.stream(flagText.split(FlagSeparator))
|
||||||
|
.map(f -> f.split("=", 2))
|
||||||
|
.filter(f -> f.length > 0)
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
entry -> entry[0],
|
||||||
|
entry -> entry.length > 1 ? entry[1] : "true" //If there's no '=' in the entry, treat it as a boolean flag.
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,27 +332,27 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSetCard(CardEdition e, CardInSet cis, CardRules cr) {
|
private void addSetCard(CardEdition e, EditionEntry cis, CardRules cr) {
|
||||||
int artIdx = IPaperCard.DEFAULT_ART_INDEX;
|
int artIdx = IPaperCard.DEFAULT_ART_INDEX;
|
||||||
String key = e.getCode() + "/" + cis.name;
|
String key = e.getCode() + "/" + cis.name();
|
||||||
if (artIds.containsKey(key)) {
|
if (artIds.containsKey(key)) {
|
||||||
artIdx = artIds.get(key) + 1;
|
artIdx = artIds.get(key) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
artIds.put(key, artIdx);
|
artIds.put(key, artIdx);
|
||||||
addCard(new PaperCard(cr, e.getCode(), cis.rarity, artIdx, false, cis.collectorNumber, cis.artistName, cis.functionalVariantName));
|
addCard(new PaperCard(cr, e.getCode(), cis.rarity(), artIdx, false, cis.collectorNumber(), cis.artistName(), cis.functionalVariantName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean addFromSetByName(String cardName, CardEdition ed, CardRules cr) {
|
private boolean addFromSetByName(String cardName, CardEdition ed, CardRules cr) {
|
||||||
List<CardInSet> cardsInSet = ed.getCardInSet(cardName); // empty collection if not present
|
List<EditionEntry> cardsInSet = ed.getCardInSet(cardName); // empty collection if not present
|
||||||
if (cr.hasFunctionalVariants()) {
|
if (cr.hasFunctionalVariants()) {
|
||||||
cardsInSet = cardsInSet.stream().filter(c -> StringUtils.isEmpty(c.functionalVariantName)
|
cardsInSet = cardsInSet.stream().filter(c -> StringUtils.isEmpty(c.functionalVariantName())
|
||||||
|| cr.getSupportedFunctionalVariants().contains(c.functionalVariantName)
|
|| cr.getSupportedFunctionalVariants().contains(c.functionalVariantName())
|
||||||
).collect(Collectors.toList());
|
).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
if (cardsInSet.isEmpty())
|
if (cardsInSet.isEmpty())
|
||||||
return false;
|
return false;
|
||||||
for (CardInSet cis : cardsInSet) {
|
for (EditionEntry cis : cardsInSet) {
|
||||||
addSetCard(ed, cis, cr);
|
addSetCard(ed, cis, cr);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -359,15 +397,15 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
upcomingSet = e;
|
upcomingSet = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
|
for (CardEdition.EditionEntry cis : e.getAllCardsInSet()) {
|
||||||
CardRules cr = rulesByName.get(cis.name);
|
CardRules cr = rulesByName.get(cis.name());
|
||||||
if (cr == null) {
|
if (cr == null) {
|
||||||
missingCards.add(cis.name);
|
missingCards.add(cis.name());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (cr.hasFunctionalVariants()) {
|
if (cr.hasFunctionalVariants()) {
|
||||||
if (StringUtils.isNotEmpty(cis.functionalVariantName)
|
if (StringUtils.isNotEmpty(cis.functionalVariantName())
|
||||||
&& !cr.getSupportedFunctionalVariants().contains(cis.functionalVariantName)) {
|
&& !cr.getSupportedFunctionalVariants().contains(cis.functionalVariantName())) {
|
||||||
//Supported card, unsupported variant.
|
//Supported card, unsupported variant.
|
||||||
//Could note the card as missing but since these are often un-cards,
|
//Could note the card as missing but since these are often un-cards,
|
||||||
//it's likely absent because it does something out of scope.
|
//it's likely absent because it does something out of scope.
|
||||||
@@ -406,7 +444,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
addCard(new PaperCard(cr, upcomingSet.getCode(), CardRarity.Unknown));
|
addCard(new PaperCard(cr, upcomingSet.getCode(), CardRarity.Unknown));
|
||||||
} else if (enableUnknownCards && !this.filtered.contains(cr.getName())) {
|
} else if (enableUnknownCards && !this.filtered.contains(cr.getName())) {
|
||||||
System.err.println("The card " + cr.getName() + " was not assigned to any set. Adding it to UNKNOWN set... to fix see res/editions/ folder. ");
|
System.err.println("The card " + cr.getName() + " was not assigned to any set. Adding it to UNKNOWN set... to fix see res/editions/ folder. ");
|
||||||
addCard(new PaperCard(cr, CardEdition.UNKNOWN.getCode(), CardRarity.Special));
|
addCard(new PaperCard(cr, CardEdition.UNKNOWN_CODE, CardRarity.Special));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.err.println("The custom card " + cr.getName() + " was not assigned to any set. Adding it to custom USER set, and will try to load custom art from USER edition.");
|
System.err.println("The custom card " + cr.getName() + " was not assigned to any set. Adding it to custom USER set, and will try to load custom art from USER edition.");
|
||||||
@@ -425,8 +463,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
lang = new LangEnglish();
|
lang = new LangEnglish();
|
||||||
}
|
}
|
||||||
// for now just check Universes Within
|
// for now just check Universes Within
|
||||||
for (CardInSet cis : editions.get("SLX").getCards()) {
|
for (EditionEntry cis : editions.get("SLX").getCards()) {
|
||||||
String orgName = alternateName.get(cis.name);
|
String orgName = alternateName.get(cis.name());
|
||||||
if (orgName != null) {
|
if (orgName != null) {
|
||||||
// found original (beyond) print
|
// found original (beyond) print
|
||||||
CardRules org = getRules(orgName);
|
CardRules org = getRules(orgName);
|
||||||
@@ -456,7 +494,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
CardRules within = new CardRules(new ICardFace[] { renamedMain, renamedOther, null, null, null, null, null }, org.getSplitType(), org.getAiHints());
|
CardRules within = new CardRules(new ICardFace[] { renamedMain, renamedOther, null, null, null, null, null }, org.getSplitType(), org.getAiHints());
|
||||||
// so workshop can edit same script
|
// so workshop can edit same script
|
||||||
within.setNormalizedName(org.getNormalizedName());
|
within.setNormalizedName(org.getNormalizedName());
|
||||||
rulesByName.put(cis.name, within);
|
rulesByName.put(cis.name(), within);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -592,15 +630,15 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PaperCard getCard(final String cardName, String setCode, int artIndex, String collectorNumber) {
|
public PaperCard getCard(final String cardName, String setCode, int artIndex, Map<String, String> flags) {
|
||||||
String reqInfo = CardRequest.compose(cardName, setCode, artIndex, collectorNumber);
|
String reqInfo = CardRequest.compose(cardName, setCode, artIndex, flags);
|
||||||
CardRequest request = CardRequest.fromString(reqInfo);
|
CardRequest request = CardRequest.fromString(reqInfo);
|
||||||
return tryGetCard(request);
|
return tryGetCard(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PaperCard getCard(final String cardName, String setCode, int artIndex, Set<String> colorID) {
|
public PaperCard getCard(final String cardName, String setCode, String collectorNumber, Map<String, String> flags) {
|
||||||
String reqInfo = CardRequest.compose(cardName, setCode, artIndex, colorID);
|
String reqInfo = CardRequest.compose(cardName, setCode, collectorNumber, flags);
|
||||||
CardRequest request = CardRequest.fromString(reqInfo);
|
CardRequest request = CardRequest.fromString(reqInfo);
|
||||||
return tryGetCard(request);
|
return tryGetCard(request);
|
||||||
}
|
}
|
||||||
@@ -611,14 +649,17 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
return null;
|
return null;
|
||||||
// 1. First off, try using all possible search parameters, to narrow down the actual cards looked for.
|
// 1. First off, try using all possible search parameters, to narrow down the actual cards looked for.
|
||||||
String reqEditionCode = request.edition;
|
String reqEditionCode = request.edition;
|
||||||
if (reqEditionCode != null && reqEditionCode.length() > 0) {
|
if (reqEditionCode != null && !reqEditionCode.isEmpty()) {
|
||||||
// This get is robust even against expansion aliases (e.g. TE and TMP both valid for Tempest) -
|
// 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)
|
// MOST of the extensions have two short codes, 141 out of 221 (so far)
|
||||||
// ALSO: Set Code are always UpperCase
|
// ALSO: Set Code are always UpperCase
|
||||||
CardEdition edition = editions.get(reqEditionCode.toUpperCase());
|
CardEdition edition = editions.get(reqEditionCode.toUpperCase());
|
||||||
|
|
||||||
return this.getCardFromSet(request.cardName, edition, request.artIndex,
|
PaperCard cardFromSet = this.getCardFromSet(request.cardName, edition, request.artIndex, request.collectorNumber, request.isFoil);
|
||||||
request.collectorNumber, request.isFoil, request.colorID);
|
if(cardFromSet != null && request.flags != null)
|
||||||
|
cardFromSet = cardFromSet.copyWithFlags(request.flags);
|
||||||
|
|
||||||
|
return cardFromSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Card lookup in edition with specified filter didn't work.
|
// 2. Card lookup in edition with specified filter didn't work.
|
||||||
@@ -661,11 +702,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil) {
|
public PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil) {
|
||||||
return getCardFromSet(cardName, edition, artIndex, collectorNumber, isFoil, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil, Set<String> colorID) {
|
|
||||||
if (edition == null || cardName == null) // preview cards
|
if (edition == null || cardName == null) // preview cards
|
||||||
return null; // No cards will be returned
|
return null; // No cards will be returned
|
||||||
|
|
||||||
@@ -674,18 +710,18 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
cardName = cardNameRequest.cardName;
|
cardName = cardNameRequest.cardName;
|
||||||
isFoil = isFoil || cardNameRequest.isFoil;
|
isFoil = isFoil || cardNameRequest.isFoil;
|
||||||
|
|
||||||
List<PaperCard> candidates = getAllCards(cardName, c -> {
|
String code1 = edition.getCode(), code2 = edition.getCode2();
|
||||||
boolean artIndexFilter = true;
|
|
||||||
boolean collectorNumberFilter = true;
|
Predicate<PaperCard> filter = (c) -> {
|
||||||
boolean setFilter = c.getEdition().equalsIgnoreCase(edition.getCode()) ||
|
String ed = c.getEdition();
|
||||||
c.getEdition().equalsIgnoreCase(edition.getCode2());
|
return ed.equalsIgnoreCase(code1) || ed.equalsIgnoreCase(code2);
|
||||||
if (artIndex > 0)
|
};
|
||||||
artIndexFilter = (c.getArtIndex() == artIndex);
|
if (artIndex > 0)
|
||||||
if ((collectorNumber != null) && (collectorNumber.length() > 0)
|
filter = filter.and((c) -> artIndex == c.getArtIndex());
|
||||||
&& !(collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER)))
|
if (collectorNumber != null && !collectorNumber.isEmpty() && !collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER))
|
||||||
collectorNumberFilter = (c.getCollectorNumber().equals(collectorNumber));
|
filter = filter.and((c) -> collectorNumber.equals(c.getCollectorNumber()));
|
||||||
return setFilter && artIndexFilter && collectorNumberFilter;
|
|
||||||
});
|
List<PaperCard> candidates = getAllCards(cardName, filter);
|
||||||
if (candidates.isEmpty())
|
if (candidates.isEmpty())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -699,7 +735,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
while (!candidate.hasImage() && candidatesIterator.hasNext())
|
while (!candidate.hasImage() && candidatesIterator.hasNext())
|
||||||
candidate = candidatesIterator.next();
|
candidate = candidatesIterator.next();
|
||||||
candidate = candidate.hasImage() ? candidate : firstCandidate;
|
candidate = candidate.hasImage() ? candidate : firstCandidate;
|
||||||
return isFoil ? candidate.getFoiled().getColorIDVersion(colorID) : candidate.getColorIDVersion(colorID);
|
return isFoil ? candidate.getFoiled() : candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -742,11 +778,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
return this.tryToGetCardFromEditions(cardInfo, artPreference, artIndex, filter);
|
return this.tryToGetCardFromEditions(cardInfo, artPreference, artIndex, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public PaperCard getCardFromEditions(final String cardInfo, final CardArtPreference artPreference, int artIndex, Set<String> colorID) {
|
|
||||||
return this.tryToGetCardFromEditions(cardInfo, artPreference, artIndex, null, false, null, colorID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ===============================================
|
* ===============================================
|
||||||
* 4. SPECIALISED CARD LOOKUP BASED ON
|
* 4. SPECIALISED CARD LOOKUP BASED ON
|
||||||
@@ -820,12 +851,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private PaperCard tryToGetCardFromEditions(String cardInfo, CardArtPreference artPreference, int artIndex,
|
private PaperCard tryToGetCardFromEditions(String cardInfo, CardArtPreference artPreference, int artIndex,
|
||||||
Date releaseDate, boolean releasedBeforeFlag, Predicate<PaperCard> filter){
|
Date releaseDate, boolean releasedBeforeFlag, Predicate<PaperCard> filter) {
|
||||||
return this.tryToGetCardFromEditions(cardInfo, artPreference, artIndex, releaseDate, releasedBeforeFlag, filter, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PaperCard tryToGetCardFromEditions(String cardInfo, CardArtPreference artPreference, int artIndex,
|
|
||||||
Date releaseDate, boolean releasedBeforeFlag, Predicate<PaperCard> filter, Set<String> colorID){
|
|
||||||
if (cardInfo == null)
|
if (cardInfo == null)
|
||||||
return null;
|
return null;
|
||||||
final CardRequest cr = CardRequest.fromString(cardInfo);
|
final CardRequest cr = CardRequest.fromString(cardInfo);
|
||||||
@@ -865,7 +891,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
for (PaperCard card : cards) {
|
for (PaperCard card : cards) {
|
||||||
String setCode = card.getEdition();
|
String setCode = card.getEdition();
|
||||||
CardEdition ed;
|
CardEdition ed;
|
||||||
if (setCode.equals(CardEdition.UNKNOWN.getCode()))
|
if (setCode.equals(CardEdition.UNKNOWN_CODE))
|
||||||
ed = CardEdition.UNKNOWN;
|
ed = CardEdition.UNKNOWN;
|
||||||
else
|
else
|
||||||
ed = editions.get(card.getEdition());
|
ed = editions.get(card.getEdition());
|
||||||
@@ -906,7 +932,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
}
|
}
|
||||||
candidate = candidate.hasImage() ? candidate : firstCandidate;
|
candidate = candidate.hasImage() ? candidate : firstCandidate;
|
||||||
//If any, we're sure that at least one candidate is always returned despite it having any image
|
//If any, we're sure that at least one candidate is always returned despite it having any image
|
||||||
return cr.isFoil ? candidate.getFoiled().getColorIDVersion(colorID) : candidate.getColorIDVersion(colorID);
|
return cr.isFoil ? candidate.getFoiled() : candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1017,7 +1043,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
public static final Predicate<PaperCard> EDITION_NON_PROMO = paperCard -> {
|
public static final Predicate<PaperCard> EDITION_NON_PROMO = paperCard -> {
|
||||||
String code = paperCard.getEdition();
|
String code = paperCard.getEdition();
|
||||||
CardEdition edition = StaticData.instance().getCardEdition(code);
|
CardEdition edition = StaticData.instance().getCardEdition(code);
|
||||||
if(edition == null && code.equals("???"))
|
if(edition == null && code.equals(CardEdition.UNKNOWN_CODE))
|
||||||
return true;
|
return true;
|
||||||
return edition != null && edition.getType() != Type.PROMO;
|
return edition != null && edition.getType() != Type.PROMO;
|
||||||
};
|
};
|
||||||
@@ -1025,7 +1051,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
public static final Predicate<PaperCard> EDITION_NON_REPRINT = paperCard -> {
|
public static final Predicate<PaperCard> EDITION_NON_REPRINT = paperCard -> {
|
||||||
String code = paperCard.getEdition();
|
String code = paperCard.getEdition();
|
||||||
CardEdition edition = StaticData.instance().getCardEdition(code);
|
CardEdition edition = StaticData.instance().getCardEdition(code);
|
||||||
if(edition == null && code.equals("???"))
|
if(edition == null && code.equals(CardEdition.UNKNOWN_CODE))
|
||||||
return true;
|
return true;
|
||||||
return edition != null && Type.REPRINT_SET_TYPES.contains(edition.getType());
|
return edition != null && Type.REPRINT_SET_TYPES.contains(edition.getType());
|
||||||
};
|
};
|
||||||
@@ -1081,8 +1107,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
public Collection<PaperCard> getAllCards(CardEdition edition) {
|
public Collection<PaperCard> getAllCards(CardEdition edition) {
|
||||||
List<PaperCard> cards = Lists.newArrayList();
|
List<PaperCard> cards = Lists.newArrayList();
|
||||||
|
|
||||||
for (CardInSet cis : edition.getAllCardsInSet()) {
|
for (EditionEntry cis : edition.getAllCardsInSet()) {
|
||||||
PaperCard card = this.getCard(cis.name, edition.getCode());
|
PaperCard card = this.getCard(cis.name(), edition.getCode());
|
||||||
if (card == null) {
|
if (card == null) {
|
||||||
// Just in case the card is listed in the edition file but Forge doesn't support it
|
// Just in case the card is listed in the edition file but Forge doesn't support it
|
||||||
continue;
|
continue;
|
||||||
@@ -1126,29 +1152,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
.anyMatch(rarity::equals);
|
.anyMatch(rarity::equals);
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBuilder appendCardToStringBuilder(PaperCard card, StringBuilder sb) {
|
|
||||||
final boolean hasBadSetInfo = card.getEdition().equals(CardEdition.UNKNOWN.getCode()) || StringUtils.isBlank(card.getEdition());
|
|
||||||
sb.append(card.getName());
|
|
||||||
if (card.isFoil()) {
|
|
||||||
sb.append(CardDb.foilSuffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasBadSetInfo) {
|
|
||||||
int artCount = getArtCount(card.getName(), card.getEdition(), card.getFunctionalVariant());
|
|
||||||
sb.append(CardDb.NameSetSeparator).append(card.getEdition());
|
|
||||||
if (artCount >= IPaperCard.DEFAULT_ART_INDEX) {
|
|
||||||
sb.append(CardDb.NameSetSeparator).append(card.getArtIndex()); // indexes start at 1 to match image file name conventions
|
|
||||||
}
|
|
||||||
if (card.getColorID() != null) {
|
|
||||||
sb.append(CardDb.NameSetSeparator);
|
|
||||||
for (String color : card.getColorID())
|
|
||||||
sb.append(CardDb.colorIDPrefix).append(color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PaperCard createUnsupportedCard(String cardRequest) {
|
public PaperCard createUnsupportedCard(String cardRequest) {
|
||||||
CardRequest request = CardRequest.fromString(cardRequest);
|
CardRequest request = CardRequest.fromString(cardRequest);
|
||||||
CardEdition cardEdition = CardEdition.UNKNOWN;
|
CardEdition cardEdition = CardEdition.UNKNOWN;
|
||||||
@@ -1157,10 +1160,10 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
// May iterate over editions and find out if there is any card named 'cardRequest' but not implemented with Forge script.
|
// May iterate over editions and find out if there is any card named 'cardRequest' but not implemented with Forge script.
|
||||||
if (StringUtils.isBlank(request.edition)) {
|
if (StringUtils.isBlank(request.edition)) {
|
||||||
for (CardEdition edition : editions) {
|
for (CardEdition edition : editions) {
|
||||||
for (CardInSet cardInSet : edition.getAllCardsInSet()) {
|
for (EditionEntry cardInSet : edition.getAllCardsInSet()) {
|
||||||
if (cardInSet.name.equals(request.cardName)) {
|
if (cardInSet.name().equals(request.cardName)) {
|
||||||
cardEdition = edition;
|
cardEdition = edition;
|
||||||
cardRarity = cardInSet.rarity;
|
cardRarity = cardInSet.rarity();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1171,9 +1174,9 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
} else {
|
} else {
|
||||||
cardEdition = editions.get(request.edition);
|
cardEdition = editions.get(request.edition);
|
||||||
if (cardEdition != null) {
|
if (cardEdition != null) {
|
||||||
for (CardInSet cardInSet : cardEdition.getAllCardsInSet()) {
|
for (EditionEntry cardInSet : cardEdition.getAllCardsInSet()) {
|
||||||
if (cardInSet.name.equals(request.cardName)) {
|
if (cardInSet.name().equals(request.cardName)) {
|
||||||
cardRarity = cardInSet.rarity;
|
cardRarity = cardInSet.rarity();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1224,9 +1227,9 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
// @leriomaggio: DONE! re-using here the same strategy implemented for lazy-loading!
|
// @leriomaggio: DONE! re-using here the same strategy implemented for lazy-loading!
|
||||||
for (CardEdition e : editions.getOrderedEditions()) {
|
for (CardEdition e : editions.getOrderedEditions()) {
|
||||||
int artIdx = IPaperCard.DEFAULT_ART_INDEX;
|
int artIdx = IPaperCard.DEFAULT_ART_INDEX;
|
||||||
for (CardInSet cis : e.getCardInSet(cardName))
|
for (EditionEntry cis : e.getCardInSet(cardName))
|
||||||
paperCards.add(new PaperCard(rules, e.getCode(), cis.rarity, artIdx++, false,
|
paperCards.add(new PaperCard(rules, e.getCode(), cis.rarity(), artIdx++, false,
|
||||||
cis.collectorNumber, cis.artistName, cis.functionalVariantName));
|
cis.collectorNumber(), cis.artistName(), cis.functionalVariantName()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
String lastEdition = null;
|
String lastEdition = null;
|
||||||
@@ -1240,17 +1243,17 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
if (ed == null) {
|
if (ed == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
List<CardInSet> cardsInSet = ed.getCardInSet(cardName);
|
List<EditionEntry> cardsInSet = ed.getCardInSet(cardName);
|
||||||
if (cardsInSet.isEmpty())
|
if (cardsInSet.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
int cardInSetIndex = Math.max(artIdx-1, 0); // make sure doesn't go below zero
|
int cardInSetIndex = Math.max(artIdx-1, 0); // make sure doesn't go below zero
|
||||||
CardInSet cds = cardsInSet.get(cardInSetIndex); // use ArtIndex to get the right Coll. Number
|
EditionEntry cds = cardsInSet.get(cardInSetIndex); // use ArtIndex to get the right Coll. Number
|
||||||
paperCards.add(new PaperCard(rules, lastEdition, tuple.getValue(), artIdx++, false,
|
paperCards.add(new PaperCard(rules, lastEdition, tuple.getValue(), artIdx++, false,
|
||||||
cds.collectorNumber, cds.artistName, cds.functionalVariantName));
|
cds.collectorNumber(), cds.artistName(), cds.functionalVariantName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (paperCards.isEmpty()) {
|
if (paperCards.isEmpty()) {
|
||||||
paperCards.add(new PaperCard(rules, CardEdition.UNKNOWN.getCode(), CardRarity.Special));
|
paperCards.add(new PaperCard(rules, CardEdition.UNKNOWN_CODE, CardRarity.Special));
|
||||||
}
|
}
|
||||||
// 2. add them to db
|
// 2. add them to db
|
||||||
for (PaperCard paperCard : paperCards) {
|
for (PaperCard paperCard : paperCards) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package forge.card;
|
package forge.card;
|
||||||
|
|
||||||
import com.google.common.collect.*;
|
import com.google.common.collect.*;
|
||||||
|
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
import forge.card.CardDb.CardArtPreference;
|
import forge.card.CardDb.CardArtPreference;
|
||||||
import forge.deck.CardPool;
|
import forge.deck.CardPool;
|
||||||
@@ -165,20 +166,49 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CardInSet implements Comparable<CardInSet> {
|
private static final Map<String, String> sortableCollNumberLookup = new HashMap<>();
|
||||||
public final CardRarity rarity;
|
/**
|
||||||
public final String collectorNumber;
|
* This method implements the main strategy to allow for natural ordering of collectorNumber
|
||||||
public final String name;
|
* (i.e. "1" < "10"), overloading the default lexicographic order (i.e. "10" < "1").
|
||||||
public final String artistName;
|
* Any non-numerical parts in the input collectorNumber will be also accounted for, and attached to the
|
||||||
public final String functionalVariantName;
|
* resulting sorting key, accordingly.
|
||||||
|
*
|
||||||
|
* @param collectorNumber: Input collectorNumber tro transform in a Sorting Key
|
||||||
|
* @return A 5-digits zero-padded collector number + any non-numerical parts attached.
|
||||||
|
*/
|
||||||
|
public static String getSortableCollectorNumber(final String collectorNumber){
|
||||||
|
String inputCollNumber = collectorNumber;
|
||||||
|
if (collectorNumber == null || collectorNumber.isEmpty())
|
||||||
|
inputCollNumber = "50000"; // very big number of 5 digits to have them in last positions
|
||||||
|
|
||||||
public CardInSet(final String name, final String collectorNumber, final CardRarity rarity, final String artistName, final String functionalVariantName) {
|
String matchedCollNr = sortableCollNumberLookup.getOrDefault(inputCollNumber, null);
|
||||||
this.name = name;
|
if (matchedCollNr != null)
|
||||||
this.collectorNumber = collectorNumber;
|
return matchedCollNr;
|
||||||
this.rarity = rarity;
|
|
||||||
this.artistName = artistName;
|
// Now, for proper sorting, let's zero-pad the collector number (if integer)
|
||||||
this.functionalVariantName = functionalVariantName;
|
int collNr;
|
||||||
|
String sortableCollNr;
|
||||||
|
try {
|
||||||
|
collNr = Integer.parseInt(inputCollNumber);
|
||||||
|
sortableCollNr = String.format("%05d", collNr);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
String nonNumSub = inputCollNumber.replaceAll("[0-9]", "");
|
||||||
|
String onlyNumSub = inputCollNumber.replaceAll("[^0-9]", "");
|
||||||
|
try {
|
||||||
|
collNr = Integer.parseInt(onlyNumSub);
|
||||||
|
} catch (NumberFormatException exon) {
|
||||||
|
collNr = 0; // this is the case of ONLY-letters collector numbers
|
||||||
|
}
|
||||||
|
if ((collNr > 0) && (inputCollNumber.startsWith(onlyNumSub))) // e.g. 12a, 37+, 2018f,
|
||||||
|
sortableCollNr = String.format("%05d", collNr) + nonNumSub;
|
||||||
|
else // e.g. WS6, S1
|
||||||
|
sortableCollNr = nonNumSub + String.format("%05d", collNr);
|
||||||
}
|
}
|
||||||
|
sortableCollNumberLookup.put(inputCollNumber, sortableCollNr);
|
||||||
|
return sortableCollNr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record EditionEntry(String name, String collectorNumber, CardRarity rarity, String artistName, String functionalVariantName) implements Comparable<EditionEntry> {
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@@ -186,7 +216,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
sb.append(collectorNumber);
|
sb.append(collectorNumber);
|
||||||
sb.append(' ');
|
sb.append(' ');
|
||||||
}
|
}
|
||||||
if (rarity != CardRarity.Unknown) {
|
if (rarity != CardRarity.Unknown && rarity != CardRarity.Token) {
|
||||||
sb.append(rarity);
|
sb.append(rarity);
|
||||||
sb.append(' ');
|
sb.append(' ');
|
||||||
}
|
}
|
||||||
@@ -202,50 +232,8 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Map<String, String> sortableCollNumberLookup = new HashMap<>();
|
|
||||||
/**
|
|
||||||
* This method implements the main strategy to allow for natural ordering of collectorNumber
|
|
||||||
* (i.e. "1" < "10"), overloading the default lexicographic order (i.e. "10" < "1").
|
|
||||||
* Any non-numerical parts in the input collectorNumber will be also accounted for, and attached to the
|
|
||||||
* resulting sorting key, accordingly.
|
|
||||||
*
|
|
||||||
* @param collectorNumber: Input collectorNumber tro transform in a Sorting Key
|
|
||||||
* @return A 5-digits zero-padded collector number + any non-numerical parts attached.
|
|
||||||
*/
|
|
||||||
public static String getSortableCollectorNumber(final String collectorNumber){
|
|
||||||
String inputCollNumber = collectorNumber;
|
|
||||||
if (collectorNumber == null || collectorNumber.isEmpty())
|
|
||||||
inputCollNumber = "50000"; // very big number of 5 digits to have them in last positions
|
|
||||||
|
|
||||||
String matchedCollNr = sortableCollNumberLookup.getOrDefault(inputCollNumber, null);
|
|
||||||
if (matchedCollNr != null)
|
|
||||||
return matchedCollNr;
|
|
||||||
|
|
||||||
// Now, for proper sorting, let's zero-pad the collector number (if integer)
|
|
||||||
int collNr;
|
|
||||||
String sortableCollNr;
|
|
||||||
try {
|
|
||||||
collNr = Integer.parseInt(inputCollNumber);
|
|
||||||
sortableCollNr = String.format("%05d", collNr);
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
String nonNumSub = inputCollNumber.replaceAll("[0-9]", "");
|
|
||||||
String onlyNumSub = inputCollNumber.replaceAll("[^0-9]", "");
|
|
||||||
try {
|
|
||||||
collNr = Integer.parseInt(onlyNumSub);
|
|
||||||
} catch (NumberFormatException exon) {
|
|
||||||
collNr = 0; // this is the case of ONLY-letters collector numbers
|
|
||||||
}
|
|
||||||
if ((collNr > 0) && (inputCollNumber.startsWith(onlyNumSub))) // e.g. 12a, 37+, 2018f,
|
|
||||||
sortableCollNr = String.format("%05d", collNr) + nonNumSub;
|
|
||||||
else // e.g. WS6, S1
|
|
||||||
sortableCollNr = nonNumSub + String.format("%05d", collNr);
|
|
||||||
}
|
|
||||||
sortableCollNumberLookup.put(inputCollNumber, sortableCollNr);
|
|
||||||
return sortableCollNr;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(CardInSet o) {
|
public int compareTo(EditionEntry o) {
|
||||||
final int nameCmp = name.compareToIgnoreCase(o.name);
|
final int nameCmp = name.compareToIgnoreCase(o.name);
|
||||||
if (0 != nameCmp) {
|
if (0 != nameCmp) {
|
||||||
return nameCmp;
|
return nameCmp;
|
||||||
@@ -262,11 +250,17 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
|
|
||||||
private final static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
|
private final static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
|
||||||
public static final CardEdition UNKNOWN = new CardEdition("1990-01-01", "???", "??", Type.UNKNOWN, "Undefined", FoilType.NOT_SUPPORTED, new CardInSet[]{});
|
/**
|
||||||
|
* Equivalent to the set code of CardEdition.UNKNOWN
|
||||||
|
*/
|
||||||
|
public static final String UNKNOWN_CODE = "???";
|
||||||
|
public static final CardEdition UNKNOWN = new CardEdition("1990-01-01", UNKNOWN_CODE, "??", Type.UNKNOWN, "Undefined", FoilType.NOT_SUPPORTED, new EditionEntry[]{});
|
||||||
private Date date;
|
private Date date;
|
||||||
private String code;
|
private String code;
|
||||||
private String code2;
|
private String code2;
|
||||||
private String scryfallCode;
|
private String scryfallCode;
|
||||||
|
private String tokensCode;
|
||||||
|
private String tokenFallbackCode;
|
||||||
private String cardsLanguage;
|
private String cardsLanguage;
|
||||||
private Type type;
|
private Type type;
|
||||||
private String name;
|
private String name;
|
||||||
@@ -296,31 +290,32 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
private String doublePickDuringDraft = "";
|
private String doublePickDuringDraft = "";
|
||||||
private String[] chaosDraftThemes = new String[0];
|
private String[] chaosDraftThemes = new String[0];
|
||||||
|
|
||||||
private final ListMultimap<String, CardInSet> cardMap;
|
private final ListMultimap<String, EditionEntry> cardMap;
|
||||||
private final List<CardInSet> cardsInSet;
|
private final List<EditionEntry> cardsInSet;
|
||||||
private final Map<String, Integer> tokenNormalized;
|
private final ListMultimap<String, EditionEntry> tokenMap;
|
||||||
// custom print sheets that will be loaded lazily
|
// custom print sheets that will be loaded lazily
|
||||||
private final Map<String, List<String>> customPrintSheetsToParse;
|
private final Map<String, List<String>> customPrintSheetsToParse;
|
||||||
|
private ListMultimap<String, EditionEntry> otherMap = ArrayListMultimap.create();
|
||||||
|
|
||||||
private int boosterArts = 1;
|
private int boosterArts = 1;
|
||||||
private SealedTemplate boosterTpl = null;
|
private SealedTemplate boosterTpl = null;
|
||||||
private final Map<String, SealedTemplate> boosterTemplates = new HashMap<>();
|
private final Map<String, SealedTemplate> boosterTemplates = new HashMap<>();
|
||||||
|
|
||||||
private CardEdition(ListMultimap<String, CardInSet> cardMap, Map<String, Integer> tokens, Map<String, List<String>> customPrintSheetsToParse) {
|
private CardEdition(ListMultimap<String, EditionEntry> cardMap, ListMultimap<String, EditionEntry> tokens, Map<String, List<String>> customPrintSheetsToParse) {
|
||||||
this.cardMap = cardMap;
|
this.cardMap = cardMap;
|
||||||
this.cardsInSet = new ArrayList<>(cardMap.values());
|
this.cardsInSet = new ArrayList<>(cardMap.values());
|
||||||
Collections.sort(cardsInSet);
|
Collections.sort(cardsInSet);
|
||||||
this.tokenNormalized = tokens;
|
this.tokenMap = tokens;
|
||||||
this.customPrintSheetsToParse = customPrintSheetsToParse;
|
this.customPrintSheetsToParse = customPrintSheetsToParse;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CardEdition(CardInSet[] cards, Map<String, Integer> tokens) {
|
private CardEdition(EditionEntry[] cards, ListMultimap<String, EditionEntry> tokens) {
|
||||||
List<CardInSet> cardsList = Arrays.asList(cards);
|
List<EditionEntry> cardsList = Arrays.asList(cards);
|
||||||
this.cardMap = ArrayListMultimap.create();
|
this.cardMap = ArrayListMultimap.create();
|
||||||
this.cardMap.replaceValues("cards", cardsList);
|
this.cardMap.replaceValues("cards", cardsList);
|
||||||
this.cardsInSet = new ArrayList<>(cardsList);
|
this.cardsInSet = new ArrayList<>(cardsList);
|
||||||
Collections.sort(cardsInSet);
|
Collections.sort(cardsInSet);
|
||||||
this.tokenNormalized = tokens;
|
this.tokenMap = tokens;
|
||||||
this.customPrintSheetsToParse = new HashMap<>();
|
this.customPrintSheetsToParse = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,8 +332,8 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
* @param name the name of the set
|
* @param name the name of the set
|
||||||
* @param cards the cards in the set
|
* @param cards the cards in the set
|
||||||
*/
|
*/
|
||||||
private CardEdition(String date, String code, String code2, Type type, String name, FoilType foil, CardInSet[] cards) {
|
private CardEdition(String date, String code, String code2, Type type, String name, FoilType foil, EditionEntry[] cards) {
|
||||||
this(cards, new HashMap<>());
|
this(cards, ArrayListMultimap.create());
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.code2 = code2;
|
this.code2 = code2;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
@@ -361,6 +356,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
public String getCode() { return code; }
|
public String getCode() { return code; }
|
||||||
public String getCode2() { return code2; }
|
public String getCode2() { return code2; }
|
||||||
public String getScryfallCode() { return scryfallCode.toLowerCase(); }
|
public String getScryfallCode() { return scryfallCode.toLowerCase(); }
|
||||||
|
public String getTokensCode() { return tokensCode.toLowerCase(); }
|
||||||
public String getCardsLangCode() { return cardsLanguage.toLowerCase(); }
|
public String getCardsLangCode() { return cardsLanguage.toLowerCase(); }
|
||||||
public Type getType() { return type; }
|
public Type getType() { return type; }
|
||||||
public String getName() { return name; }
|
public String getName() { return name; }
|
||||||
@@ -385,14 +381,14 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
public String getSheetReplaceCardFromSheet2() { return sheetReplaceCardFromSheet2; }
|
public String getSheetReplaceCardFromSheet2() { return sheetReplaceCardFromSheet2; }
|
||||||
public String[] getChaosDraftThemes() { return chaosDraftThemes; }
|
public String[] getChaosDraftThemes() { return chaosDraftThemes; }
|
||||||
|
|
||||||
public List<CardInSet> getCards() { return cardMap.get(EditionSectionWithCollectorNumbers.CARDS.getName()); }
|
public List<EditionEntry> getCards() { return cardMap.get(EditionSectionWithCollectorNumbers.CARDS.getName()); }
|
||||||
public List<CardInSet> getRebalancedCards() { return cardMap.get(EditionSectionWithCollectorNumbers.REBALANCED.getName()); }
|
public List<EditionEntry> getRebalancedCards() { return cardMap.get(EditionSectionWithCollectorNumbers.REBALANCED.getName()); }
|
||||||
public List<CardInSet> getFunnyEternalCards() { return cardMap.get(EditionSectionWithCollectorNumbers.ETERNAL.getName()); }
|
public List<EditionEntry> getFunnyEternalCards() { return cardMap.get(EditionSectionWithCollectorNumbers.ETERNAL.getName()); }
|
||||||
public List<CardInSet> getAllCardsInSet() {
|
public List<EditionEntry> getAllCardsInSet() {
|
||||||
return cardsInSet;
|
return cardsInSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListMultimap<String, CardInSet> cardsInSetLookupMap = null;
|
private ListMultimap<String, EditionEntry> cardsInSetLookupMap = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all the CardInSet instances with the input card name.
|
* Get all the CardInSet instances with the input card name.
|
||||||
@@ -400,12 +396,12 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
* @return A List of all the CardInSet instances for a given name.
|
* @return A List of all the CardInSet instances for a given name.
|
||||||
* If not fount, an Empty sequence (view) will be returned instead!
|
* If not fount, an Empty sequence (view) will be returned instead!
|
||||||
*/
|
*/
|
||||||
public List<CardInSet> getCardInSet(String cardName){
|
public List<EditionEntry> getCardInSet(String cardName){
|
||||||
if (cardsInSetLookupMap == null) {
|
if (cardsInSetLookupMap == null) {
|
||||||
// initialise
|
// initialise
|
||||||
cardsInSetLookupMap = Multimaps.newListMultimap(new TreeMap<>(String.CASE_INSENSITIVE_ORDER), Lists::newArrayList);
|
cardsInSetLookupMap = Multimaps.newListMultimap(new TreeMap<>(String.CASE_INSENSITIVE_ORDER), Lists::newArrayList);
|
||||||
List<CardInSet> cardsInSet = this.getAllCardsInSet();
|
List<EditionEntry> cardsInSet = this.getAllCardsInSet();
|
||||||
for (CardInSet cis : cardsInSet){
|
for (EditionEntry cis : cardsInSet){
|
||||||
String key = cis.name;
|
String key = cis.name;
|
||||||
cardsInSetLookupMap.put(key, cis);
|
cardsInSetLookupMap.put(key, cis);
|
||||||
}
|
}
|
||||||
@@ -413,8 +409,19 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
return this.cardsInSetLookupMap.get(cardName);
|
return this.cardsInSetLookupMap.get(cardName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EditionEntry getCardFromCollectorNumber(String collectorNumber) {
|
||||||
|
if(collectorNumber == null || collectorNumber.isEmpty())
|
||||||
|
return null;
|
||||||
|
for(EditionEntry c : this.cardsInSet) {
|
||||||
|
//Could build a map for this one too if it's used for more than one-offs.
|
||||||
|
if (c.collectorNumber.equalsIgnoreCase(collectorNumber))
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isRebalanced(String cardName) {
|
public boolean isRebalanced(String cardName) {
|
||||||
for (CardInSet cis : getRebalancedCards()) {
|
for (EditionEntry cis : getRebalancedCards()) {
|
||||||
if (cis.name.equals(cardName)) {
|
if (cis.name.equals(cardName)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -424,7 +431,44 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
|
|
||||||
public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others
|
public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others
|
||||||
|
|
||||||
public Map<String, Integer> getTokens() { return tokenNormalized; }
|
public Multimap<String, EditionEntry> getTokens() { return tokenMap; }
|
||||||
|
|
||||||
|
public EditionEntry getTokenFromCollectorNumber(String collectorNumber) {
|
||||||
|
if(collectorNumber == null || collectorNumber.isEmpty())
|
||||||
|
return null;
|
||||||
|
for(EditionEntry c : this.tokenMap.values()) {
|
||||||
|
//Could build a map for this one too if it's used for more than one-offs.
|
||||||
|
if (c.collectorNumber.equalsIgnoreCase(collectorNumber))
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTokenSet(String token) {
|
||||||
|
if (tokenMap.containsKey(token)) {
|
||||||
|
return this.getCode();
|
||||||
|
}
|
||||||
|
if (this.tokenFallbackCode != null) {
|
||||||
|
return StaticData.instance().getCardEdition(this.tokenFallbackCode).getTokenSet(token);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public String getOtherSet(String token) {
|
||||||
|
if (otherMap.containsKey(token)) {
|
||||||
|
return this.getCode();
|
||||||
|
}
|
||||||
|
if (this.tokenFallbackCode != null) {
|
||||||
|
return StaticData.instance().getCardEdition(this.tokenFallbackCode).getOtherSet(token);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EditionEntry findOther(String name) {
|
||||||
|
if (otherMap.containsKey(name)) {
|
||||||
|
return Aggregates.random(otherMap.get(name));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(final CardEdition o) {
|
public int compareTo(final CardEdition o) {
|
||||||
@@ -508,8 +552,8 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
for (String sectionName : cardMap.keySet()) {
|
for (String sectionName : cardMap.keySet()) {
|
||||||
PrintSheet sheet = new PrintSheet(String.format("%s %s", this.getCode(), sectionName));
|
PrintSheet sheet = new PrintSheet(String.format("%s %s", this.getCode(), sectionName));
|
||||||
|
|
||||||
List<CardInSet> cards = cardMap.get(sectionName);
|
List<EditionEntry> cards = cardMap.get(sectionName);
|
||||||
for (CardInSet card : cards) {
|
for (EditionEntry card : cards) {
|
||||||
int index = 1;
|
int index = 1;
|
||||||
if (cardToIndex.containsKey(card.name)) {
|
if (cardToIndex.containsKey(card.name)) {
|
||||||
index = cardToIndex.get(card.name) + 1;
|
index = cardToIndex.get(card.name) + 1;
|
||||||
@@ -562,6 +606,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
it should also match the Un-set and older alternate art cards
|
it should also match the Un-set and older alternate art cards
|
||||||
like Merseine from FEM.
|
like Merseine from FEM.
|
||||||
*/
|
*/
|
||||||
|
// Collector numbers now should allow hyphens for Planeswalker Championship Promos
|
||||||
//"(^(?<cnum>[0-9]+.?) )?((?<rarity>[SCURML]) )?(?<name>.*)$"
|
//"(^(?<cnum>[0-9]+.?) )?((?<rarity>[SCURML]) )?(?<name>.*)$"
|
||||||
/* Ideally we'd use the named group above, but Android 6 and
|
/* Ideally we'd use the named group above, but Android 6 and
|
||||||
earlier don't appear to support named groups.
|
earlier don't appear to support named groups.
|
||||||
@@ -575,12 +620,20 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
* functional variant name - grouping #9
|
* functional variant name - grouping #9
|
||||||
*/
|
*/
|
||||||
// "(^(.?[0-9A-Z]+.?))?(([SCURML]) )?(.*)$"
|
// "(^(.?[0-9A-Z]+.?))?(([SCURML]) )?(.*)$"
|
||||||
"(^(.?[0-9A-Z]+\\S?[A-Z]*)\\s)?(([SCURML])\\s)?([^@\\$]*)( @([^\\$]*))?( \\$(.+))?$"
|
"(^(.?[0-9A-Z-]+\\S?[A-Z]*)\\s)?(([SCURML])\\s)?([^@\\$]*)( @([^\\$]*))?( \\$(.+))?$"
|
||||||
);
|
);
|
||||||
|
|
||||||
ListMultimap<String, CardInSet> cardMap = ArrayListMultimap.create();
|
final Pattern tokenPattern = Pattern.compile(
|
||||||
|
/*
|
||||||
|
* cnum - grouping #2
|
||||||
|
* name - grouping #3
|
||||||
|
* artist name - grouping #5
|
||||||
|
*/
|
||||||
|
"(^(.?[0-9A-Z]+\\S?[A-Z]*)\\s)?([^@]*)( @(.*))?$"
|
||||||
|
);
|
||||||
|
|
||||||
|
ListMultimap<String, EditionEntry> cardMap = ArrayListMultimap.create();
|
||||||
List<BoosterSlot> boosterSlots = null;
|
List<BoosterSlot> boosterSlots = null;
|
||||||
Map<String, Integer> tokenNormalized = new HashMap<>();
|
|
||||||
Map<String, List<String>> customPrintSheetsToParse = new HashMap<>();
|
Map<String, List<String>> customPrintSheetsToParse = new HashMap<>();
|
||||||
List<String> editionSectionsWithCollectorNumbers = EditionSectionWithCollectorNumbers.getNames();
|
List<String> editionSectionsWithCollectorNumbers = EditionSectionWithCollectorNumbers.getNames();
|
||||||
|
|
||||||
@@ -611,7 +664,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
String cardName = matcher.group(5);
|
String cardName = matcher.group(5);
|
||||||
String artistName = matcher.group(7);
|
String artistName = matcher.group(7);
|
||||||
String functionalVariantName = matcher.group(9);
|
String functionalVariantName = matcher.group(9);
|
||||||
CardInSet cis = new CardInSet(cardName, collectorNumber, r, artistName, functionalVariantName);
|
EditionEntry cis = new EditionEntry(cardName, collectorNumber, r, artistName, functionalVariantName);
|
||||||
|
|
||||||
cardMap.put(sectionName, cis);
|
cardMap.put(sectionName, cis);
|
||||||
}
|
}
|
||||||
@@ -625,41 +678,59 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ListMultimap<String, EditionEntry> tokenMap = ArrayListMultimap.create();
|
||||||
|
ListMultimap<String, EditionEntry> otherMap = ArrayListMultimap.create();
|
||||||
// parse tokens section
|
// parse tokens section
|
||||||
if (contents.containsKey("tokens")) {
|
if (contents.containsKey("tokens")) {
|
||||||
for (String line : contents.get("tokens")) {
|
for (String line : contents.get("tokens")) {
|
||||||
if (StringUtils.isBlank(line))
|
if (StringUtils.isBlank(line))
|
||||||
continue;
|
continue;
|
||||||
|
Matcher matcher = tokenPattern.matcher(line);
|
||||||
|
|
||||||
if (!tokenNormalized.containsKey(line)) {
|
if (!matcher.matches()) {
|
||||||
tokenNormalized.put(line, 1);
|
continue;
|
||||||
} else {
|
|
||||||
tokenNormalized.put(line, tokenNormalized.get(line) + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String collectorNumber = matcher.group(2);
|
||||||
|
String cardName = matcher.group(3);
|
||||||
|
String artistName = matcher.group(5);
|
||||||
|
// rarity isn't used for this anyway
|
||||||
|
EditionEntry tis = new EditionEntry(cardName, collectorNumber, CardRarity.Token, artistName, null);
|
||||||
|
tokenMap.put(cardName, tis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (contents.containsKey("other")) {
|
||||||
|
for (String line : contents.get("other")) {
|
||||||
|
if (StringUtils.isBlank(line))
|
||||||
|
continue;
|
||||||
|
Matcher matcher = tokenPattern.matcher(line);
|
||||||
|
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String collectorNumber = matcher.group(2);
|
||||||
|
String cardName = matcher.group(3);
|
||||||
|
String artistName = matcher.group(5);
|
||||||
|
EditionEntry tis = new EditionEntry(cardName, collectorNumber, CardRarity.Unknown, artistName, null);
|
||||||
|
otherMap.put(cardName, tis);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CardEdition res = new CardEdition(cardMap, tokenNormalized, customPrintSheetsToParse);
|
CardEdition res = new CardEdition(cardMap, tokenMap, customPrintSheetsToParse);
|
||||||
res.boosterSlots = boosterSlots;
|
res.boosterSlots = boosterSlots;
|
||||||
// parse metadata section
|
// parse metadata section
|
||||||
res.name = metadata.get("name");
|
res.name = metadata.get("name");
|
||||||
res.date = parseDate(metadata.get("date"));
|
res.date = parseDate(metadata.get("date"));
|
||||||
res.code = metadata.get("code");
|
res.code = metadata.get("code");
|
||||||
res.code2 = metadata.get("code2");
|
res.code2 = metadata.get("code2", res.code);
|
||||||
if (res.code2 == null) {
|
res.scryfallCode = metadata.get("ScryfallCode", res.code);
|
||||||
res.code2 = res.code;
|
res.tokensCode = metadata.get("TokensCode", "T" + res.scryfallCode);
|
||||||
}
|
res.tokenFallbackCode = metadata.get("TokenFallbackCode");
|
||||||
res.scryfallCode = metadata.get("ScryfallCode");
|
res.cardsLanguage = metadata.get("CardLang", "en");
|
||||||
if (res.scryfallCode == null) {
|
|
||||||
res.scryfallCode = res.code;
|
|
||||||
}
|
|
||||||
res.cardsLanguage = metadata.get("CardLang");
|
|
||||||
if (res.cardsLanguage == null) {
|
|
||||||
res.cardsLanguage = "en";
|
|
||||||
}
|
|
||||||
|
|
||||||
res.boosterArts = metadata.getInt("BoosterCovers", 1);
|
res.boosterArts = metadata.getInt("BoosterCovers", 1);
|
||||||
|
|
||||||
|
res.otherMap = otherMap;
|
||||||
|
|
||||||
String boosterDesc = metadata.get("Booster");
|
String boosterDesc = metadata.get("Booster");
|
||||||
|
|
||||||
if (metadata.contains("Booster")) {
|
if (metadata.contains("Booster")) {
|
||||||
@@ -778,7 +849,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
initAliases(E); //Made a method in case the system changes, so it's consistent.
|
initAliases(E); //Made a method in case the system changes, so it's consistent.
|
||||||
}
|
}
|
||||||
CardEdition customBucket = new CardEdition("2990-01-01", "USER", "USER",
|
CardEdition customBucket = new CardEdition("2990-01-01", "USER", "USER",
|
||||||
Type.CUSTOM_SET, "USER", FoilType.NOT_SUPPORTED, new CardInSet[]{});
|
Type.CUSTOM_SET, "USER", FoilType.NOT_SUPPORTED, new EditionEntry[]{});
|
||||||
this.add(customBucket);
|
this.add(customBucket);
|
||||||
initAliases(customBucket);
|
initAliases(customBucket);
|
||||||
this.lock = true; //Consider it initialized and prevent from writing any more data.
|
this.lock = true; //Consider it initialized and prevent from writing any more data.
|
||||||
@@ -810,7 +881,7 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
|
|
||||||
public CardEdition getEditionByCodeOrThrow(final String code) {
|
public CardEdition getEditionByCodeOrThrow(final String code) {
|
||||||
final CardEdition set = this.get(code);
|
final CardEdition set = this.get(code);
|
||||||
if (null == set && code.equals("???")) //Hardcoded set ??? is not with the others, needs special check.
|
if (null == set && code.equals(UNKNOWN_CODE)) //Hardcoded set ??? is not with the others, needs special check.
|
||||||
return UNKNOWN;
|
return UNKNOWN;
|
||||||
if (null == set) {
|
if (null == set) {
|
||||||
throw new RuntimeException("Edition with code '" + code + "' not found");
|
throw new RuntimeException("Edition with code '" + code + "' not found");
|
||||||
@@ -941,4 +1012,12 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasBasicLands() {
|
||||||
|
for(String landName : MagicColor.Constant.BASIC_LANDS) {
|
||||||
|
if (null == StaticData.instance().getCommonCards().getCard(landName, this.getCode(), 0))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ package forge.card;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
import forge.StaticData;
|
||||||
import forge.card.mana.IParserManaCost;
|
import forge.card.mana.IParserManaCost;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
import forge.card.mana.ManaCostShard;
|
import forge.card.mana.ManaCostShard;
|
||||||
@@ -149,6 +151,10 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
return splitType;
|
return splitType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasBackSide() {
|
||||||
|
return CardSplitType.DUAL_FACED_CARDS.contains(splitType) || splitType == CardSplitType.Flip;
|
||||||
|
}
|
||||||
|
|
||||||
public ICardFace getMainPart() {
|
public ICardFace getMainPart() {
|
||||||
return mainPart;
|
return mainPart;
|
||||||
}
|
}
|
||||||
@@ -165,20 +171,32 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
return Iterables.concat(Arrays.asList(mainPart, otherPart), specializedParts.values());
|
return Iterables.concat(Arrays.asList(mainPart, otherPart), specializedParts.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICardFace getWSpecialize() {
|
public String getImageName(CardStateName state) {
|
||||||
return specializedParts.get(CardStateName.SpecializeW);
|
if (splitType == CardSplitType.Split) {
|
||||||
}
|
return mainPart.getName() + otherPart.getName();
|
||||||
public ICardFace getUSpecialize() {
|
} else if (state.equals(splitType.getChangedStateName())) {
|
||||||
return specializedParts.get(CardStateName.SpecializeU);
|
if (otherPart != null) {
|
||||||
}
|
return otherPart.getName();
|
||||||
public ICardFace getBSpecialize() {
|
} else if (this.hasBackSide()) {
|
||||||
return specializedParts.get(CardStateName.SpecializeB);
|
if (!getMeldWith().isEmpty()) {
|
||||||
}
|
final CardDb db = StaticData.instance().getCommonCards();
|
||||||
public ICardFace getRSpecialize() {
|
return db.getRules(getMeldWith()).getOtherPart().getName();
|
||||||
return specializedParts.get(CardStateName.SpecializeR);
|
}
|
||||||
}
|
return null;
|
||||||
public ICardFace getGSpecialize() {
|
}
|
||||||
return specializedParts.get(CardStateName.SpecializeG);
|
}
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case SpecializeW:
|
||||||
|
case SpecializeU:
|
||||||
|
case SpecializeB:
|
||||||
|
case SpecializeR:
|
||||||
|
case SpecializeG:
|
||||||
|
ICardFace face = specializedParts.get(state);
|
||||||
|
return face != null ? face.getName() : null;
|
||||||
|
default:
|
||||||
|
return getName();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@@ -396,6 +414,7 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getSetColorID() {
|
public int getSetColorID() {
|
||||||
|
//Could someday generalize this to support other kinds of markings.
|
||||||
return setColorID;
|
return setColorID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ public enum CardSplitType
|
|||||||
Meld(FaceSelectionMethod.USE_ACTIVE_FACE, CardStateName.Meld),
|
Meld(FaceSelectionMethod.USE_ACTIVE_FACE, CardStateName.Meld),
|
||||||
Split(FaceSelectionMethod.COMBINE, CardStateName.RightSplit),
|
Split(FaceSelectionMethod.COMBINE, CardStateName.RightSplit),
|
||||||
Flip(FaceSelectionMethod.USE_PRIMARY_FACE, CardStateName.Flipped),
|
Flip(FaceSelectionMethod.USE_PRIMARY_FACE, CardStateName.Flipped),
|
||||||
Adventure(FaceSelectionMethod.USE_PRIMARY_FACE, CardStateName.Adventure),
|
Adventure(FaceSelectionMethod.USE_PRIMARY_FACE, CardStateName.Secondary),
|
||||||
|
Omen(FaceSelectionMethod.USE_PRIMARY_FACE, CardStateName.Secondary),
|
||||||
Modal(FaceSelectionMethod.USE_ACTIVE_FACE, CardStateName.Modal),
|
Modal(FaceSelectionMethod.USE_ACTIVE_FACE, CardStateName.Modal),
|
||||||
Specialize(FaceSelectionMethod.USE_ACTIVE_FACE, null);
|
Specialize(FaceSelectionMethod.USE_ACTIVE_FACE, null);
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ public enum CardStateName {
|
|||||||
Original,
|
Original,
|
||||||
FaceDown,
|
FaceDown,
|
||||||
Flipped,
|
Flipped,
|
||||||
Converted,
|
|
||||||
Transformed,
|
Transformed,
|
||||||
Meld,
|
Meld,
|
||||||
LeftSplit,
|
LeftSplit,
|
||||||
RightSplit,
|
RightSplit,
|
||||||
Adventure,
|
Secondary,
|
||||||
Modal,
|
Modal,
|
||||||
EmptyRoom,
|
EmptyRoom,
|
||||||
SpecializeW,
|
SpecializeW,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -315,6 +316,12 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
return landTypes;
|
return landTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<String> getBattleTypes() {
|
||||||
|
if(!isBattle())
|
||||||
|
return Set.of();
|
||||||
|
return subtypes.stream().filter(CardType::isABattleType).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasStringType(String t) {
|
public boolean hasStringType(String t) {
|
||||||
if (t.isEmpty()) {
|
if (t.isEmpty()) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public interface CardTypeView extends Iterable<String>, Serializable {
|
|||||||
|
|
||||||
Set<String> getCreatureTypes();
|
Set<String> getCreatureTypes();
|
||||||
Set<String> getLandTypes();
|
Set<String> getLandTypes();
|
||||||
|
Set<String> getBattleTypes();
|
||||||
|
|
||||||
boolean hasStringType(String t);
|
boolean hasStringType(String t);
|
||||||
boolean hasType(CoreType type);
|
boolean hasType(CoreType type);
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import forge.util.BinaryUtil;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>CardColor class.</p>
|
* <p>CardColor class.</p>
|
||||||
@@ -291,14 +293,8 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
if (this.orderWeight == -1) {
|
final ManaCostShard[] orderedShards = getOrderedShards();
|
||||||
return "n/a";
|
return Arrays.stream(orderedShards).map(ManaCostShard::toShortString).collect(Collectors.joining());
|
||||||
}
|
|
||||||
final String toReturn = MagicColor.toLongString(myColor);
|
|
||||||
if (toReturn.equals(MagicColor.Constant.COLORLESS) && myColor != 0) {
|
|
||||||
return "multi";
|
|
||||||
}
|
|
||||||
return toReturn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -376,6 +372,10 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Stream<MagicColor.Color> stream() {
|
||||||
|
return this.toEnumSet().stream();
|
||||||
|
}
|
||||||
|
|
||||||
//Get array of mana cost shards for color set in the proper order
|
//Get array of mana cost shards for color set in the proper order
|
||||||
public ManaCostShard[] getOrderedShards() {
|
public ManaCostShard[] getOrderedShards() {
|
||||||
return shardOrderLookup[myColor];
|
return shardOrderLookup[myColor];
|
||||||
|
|||||||
@@ -5,43 +5,42 @@ import forge.item.PaperCard;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic Cards Database.
|
||||||
|
* --------------------
|
||||||
|
* This interface defines the general API for Database Access and Cards' Lookup.
|
||||||
|
* <p>
|
||||||
|
* Methods for single Card's lookup currently support three alternative strategies:
|
||||||
|
* 1. [getCard]: Card search based on a single card's attributes
|
||||||
|
* (i.e. name, edition, art, collectorNumber)
|
||||||
|
* <p>
|
||||||
|
* 2. [getCardFromSet]: Card Lookup from a single Expansion set.
|
||||||
|
* Particularly useful in Deck Editors when a specific Set is specified.
|
||||||
|
* <p>
|
||||||
|
* 3. [getCardFromEditions]: Card search considering a predefined `SetPreference` policy and/or a specified Date
|
||||||
|
* when no expansion is specified for a card.
|
||||||
|
* This method is particularly useful for Re-prints whenever no specific
|
||||||
|
* Expansion is specified (e.g. in Deck Import) and a decision should be made
|
||||||
|
* on which card to pick. This methods allows to adopt a SetPreference selection
|
||||||
|
* policy to make this decision.
|
||||||
|
* <p>
|
||||||
|
* The API also includes methods to fetch Collection of Card (i.e. PaperCard instances):
|
||||||
|
* - all cards (no filter)
|
||||||
|
* - all unique cards (by name)
|
||||||
|
* - all prints of a single card
|
||||||
|
* - all cards from a single Expansion Set
|
||||||
|
* - all cards compliant with a filter condition (i.e. Predicate)
|
||||||
|
* <p>
|
||||||
|
* Finally, various utility methods are supported:
|
||||||
|
* - Get the foil version of a Card (if Any)
|
||||||
|
* - Get the Order Number of a Card in an Expansion Set
|
||||||
|
* - Get the number of Print/Arts for a card in a Set (useful for those exp. having multiple arts)
|
||||||
|
* */
|
||||||
public interface ICardDatabase extends Iterable<PaperCard> {
|
public interface ICardDatabase extends Iterable<PaperCard> {
|
||||||
/**
|
|
||||||
* Magic Cards Database.
|
|
||||||
* --------------------
|
|
||||||
* This interface defines the general API for Database Access and Cards' Lookup.
|
|
||||||
*
|
|
||||||
* Methods for single Card's lookup currently support three alternative strategies:
|
|
||||||
* 1. [getCard]: Card search based on a single card's attributes
|
|
||||||
* (i.e. name, edition, art, collectorNumber)
|
|
||||||
*
|
|
||||||
* 2. [getCardFromSet]: Card Lookup from a single Expansion set.
|
|
||||||
* Particularly useful in Deck Editors when a specific Set is specified.
|
|
||||||
*
|
|
||||||
* 3. [getCardFromEditions]: Card search considering a predefined `SetPreference` policy and/or a specified Date
|
|
||||||
* when no expansion is specified for a card.
|
|
||||||
* This method is particularly useful for Re-prints whenever no specific
|
|
||||||
* Expansion is specified (e.g. in Deck Import) and a decision should be made
|
|
||||||
* on which card to pick. This methods allows to adopt a SetPreference selection
|
|
||||||
* policy to make this decision.
|
|
||||||
*
|
|
||||||
* The API also includes methods to fetch Collection of Card (i.e. PaperCard instances):
|
|
||||||
* - all cards (no filter)
|
|
||||||
* - all unique cards (by name)
|
|
||||||
* - all prints of a single card
|
|
||||||
* - all cards from a single Expansion Set
|
|
||||||
* - all cards compliant with a filter condition (i.e. Predicate)
|
|
||||||
*
|
|
||||||
* Finally, various utility methods are supported:
|
|
||||||
* - Get the foil version of a Card (if Any)
|
|
||||||
* - Get the Order Number of a Card in an Expansion Set
|
|
||||||
* - Get the number of Print/Arts for a card in a Set (useful for those exp. having multiple arts)
|
|
||||||
* */
|
|
||||||
|
|
||||||
/* SINGLE CARD RETRIEVAL METHODS
|
/* SINGLE CARD RETRIEVAL METHODS
|
||||||
* ============================= */
|
* ============================= */
|
||||||
// 1. Card Lookup by attributes
|
// 1. Card Lookup by attributes
|
||||||
@@ -50,22 +49,20 @@ public interface ICardDatabase extends Iterable<PaperCard> {
|
|||||||
PaperCard getCard(String cardName, String edition, int artIndex);
|
PaperCard getCard(String cardName, String edition, int artIndex);
|
||||||
// [NEW Methods] Including the card CollectorNumber as criterion for DB lookup
|
// [NEW Methods] Including the card CollectorNumber as criterion for DB lookup
|
||||||
PaperCard getCard(String cardName, String edition, String collectorNumber);
|
PaperCard getCard(String cardName, String edition, String collectorNumber);
|
||||||
PaperCard getCard(String cardName, String edition, int artIndex, String collectorNumber);
|
PaperCard getCard(String cardName, String edition, int artIndex, Map<String, String> flags);
|
||||||
PaperCard getCard(String cardName, String edition, int artIndex, Set<String> colorID);
|
PaperCard getCard(String cardName, String edition, String collectorNumber, Map<String, String> flags);
|
||||||
|
|
||||||
// 2. Card Lookup from a single Expansion Set
|
// 2. Card Lookup from a single Expansion Set
|
||||||
PaperCard getCardFromSet(String cardName, CardEdition edition, boolean isFoil); // NOT yet used, included for API symmetry
|
PaperCard getCardFromSet(String cardName, CardEdition edition, boolean isFoil); // NOT yet used, included for API symmetry
|
||||||
PaperCard getCardFromSet(String cardName, CardEdition edition, String collectorNumber, boolean isFoil);
|
PaperCard getCardFromSet(String cardName, CardEdition edition, String collectorNumber, boolean isFoil);
|
||||||
PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, boolean isFoil);
|
PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, boolean isFoil);
|
||||||
PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil);
|
PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil);
|
||||||
PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil, Set<String> colorID);
|
|
||||||
|
|
||||||
// 3. Card lookup based on CardArtPreference Selection Policy
|
// 3. Card lookup based on CardArtPreference Selection Policy
|
||||||
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference);
|
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference);
|
||||||
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, Predicate<PaperCard> filter);
|
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, Predicate<PaperCard> filter);
|
||||||
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex);
|
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex);
|
||||||
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex, Predicate<PaperCard> filter);
|
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex, Predicate<PaperCard> filter);
|
||||||
PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex, Set<String> colorID);
|
|
||||||
|
|
||||||
// 4. Specialised Card Lookup on CardArtPreference Selection and Release Date
|
// 4. Specialised Card Lookup on CardArtPreference Selection and Release Date
|
||||||
PaperCard getCardFromEditionsReleasedBefore(String cardName, CardArtPreference artPreference, Date releaseDate);
|
PaperCard getCardFromEditionsReleasedBefore(String cardName, CardArtPreference artPreference, Date releaseDate);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package forge.card;
|
package forge.card;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import forge.deck.DeckRecognizer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds byte values for each color magic has.
|
* Holds byte values for each color magic has.
|
||||||
@@ -187,6 +188,12 @@ public final class MagicColor {
|
|||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getLocalizedName() {
|
||||||
|
//Should probably move some of this logic back here, or at least to a more general location.
|
||||||
|
return DeckRecognizer.getLocalisedMagicColorName(getName());
|
||||||
|
}
|
||||||
|
|
||||||
public byte getColormask() {
|
public byte getColormask() {
|
||||||
return colormask;
|
return colormask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ public enum ManaCostShard {
|
|||||||
/** The cmpc. */
|
/** The cmpc. */
|
||||||
private final float cmpc;
|
private final float cmpc;
|
||||||
private final String stringValue;
|
private final String stringValue;
|
||||||
|
private final String shortStringValue;
|
||||||
|
|
||||||
/** The image key. */
|
/** The image key. */
|
||||||
private final String imageKey;
|
private final String imageKey;
|
||||||
@@ -125,6 +126,7 @@ public enum ManaCostShard {
|
|||||||
this.cmc = this.getCMC();
|
this.cmc = this.getCMC();
|
||||||
this.cmpc = this.getCmpCost();
|
this.cmpc = this.getCmpCost();
|
||||||
this.stringValue = "{" + sValue + "}";
|
this.stringValue = "{" + sValue + "}";
|
||||||
|
this.shortStringValue = sValue;
|
||||||
this.imageKey = imgKey;
|
this.imageKey = imgKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,16 +234,21 @@ public enum ManaCostShard {
|
|||||||
return ManaCostShard.valueOf(atoms);
|
return ManaCostShard.valueOf(atoms);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* (non-Javadoc)
|
* @return the string representation of this shard - e.g. "{W}" "{2/U}" "{G/P}"
|
||||||
*
|
|
||||||
* @see java.lang.Object#toString()
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final String toString() {
|
public final String toString() {
|
||||||
return this.stringValue;
|
return this.stringValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The string representation of this shard without brackets - e.g. "W" "2/U" "G/P"
|
||||||
|
*/
|
||||||
|
public final String toShortString() {
|
||||||
|
return this.shortStringValue;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the cmc.
|
* Gets the cmc.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -52,7 +52,10 @@ public class CardPool extends ItemPool<PaperCard> {
|
|||||||
|
|
||||||
public void add(final String cardRequest, final int amount) {
|
public void add(final String cardRequest, final int amount) {
|
||||||
CardDb.CardRequest request = CardDb.CardRequest.fromString(cardRequest);
|
CardDb.CardRequest request = CardDb.CardRequest.fromString(cardRequest);
|
||||||
this.add(CardDb.CardRequest.compose(request.cardName, request.isFoil), request.edition, request.artIndex, amount, false, request.colorID);
|
if(request.collectorNumber != null && !request.collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER))
|
||||||
|
this.add(CardDb.CardRequest.compose(request.cardName, request.isFoil), request.edition, request.collectorNumber, amount, false, request.flags);
|
||||||
|
else
|
||||||
|
this.add(CardDb.CardRequest.compose(request.cardName, request.isFoil), request.edition, request.artIndex, amount, false, request.flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(final String cardName, final String setCode) {
|
public void add(final String cardName, final String setCode) {
|
||||||
@@ -71,7 +74,20 @@ public class CardPool extends ItemPool<PaperCard> {
|
|||||||
public void add(String cardName, String setCode, int artIndex, final int amount) {
|
public void add(String cardName, String setCode, int artIndex, final int amount) {
|
||||||
this.add(cardName, setCode, artIndex, amount, false, null);
|
this.add(cardName, setCode, artIndex, amount, false, null);
|
||||||
}
|
}
|
||||||
public void add(String cardName, String setCode, int artIndex, final int amount, boolean addAny, Set<String> colorID) {
|
private void add(String cardName, String setCode, String collectorNumber, final int amount, boolean addAny, Map<String, String> flags) {
|
||||||
|
Map<String, CardDb> dbs = StaticData.instance().getAvailableDatabases();
|
||||||
|
for (Map.Entry<String, CardDb> entry: dbs.entrySet()){
|
||||||
|
CardDb db = entry.getValue();
|
||||||
|
PaperCard paperCard = db.getCard(cardName, setCode, collectorNumber, flags);
|
||||||
|
if (paperCard != null) {
|
||||||
|
this.add(paperCard, amount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Failed to find it. Fall back accordingly?
|
||||||
|
this.add(cardName, setCode, IPaperCard.NO_ART_INDEX, amount, addAny, flags);
|
||||||
|
}
|
||||||
|
private void add(String cardName, String setCode, int artIndex, final int amount, boolean addAny, Map<String, String> flags) {
|
||||||
Map<String, CardDb> dbs = StaticData.instance().getAvailableDatabases();
|
Map<String, CardDb> dbs = StaticData.instance().getAvailableDatabases();
|
||||||
PaperCard paperCard = null;
|
PaperCard paperCard = null;
|
||||||
String selectedDbName = "";
|
String selectedDbName = "";
|
||||||
@@ -81,7 +97,7 @@ public class CardPool extends ItemPool<PaperCard> {
|
|||||||
for (Map.Entry<String, CardDb> entry: dbs.entrySet()){
|
for (Map.Entry<String, CardDb> entry: dbs.entrySet()){
|
||||||
String dbName = entry.getKey();
|
String dbName = entry.getKey();
|
||||||
CardDb db = entry.getValue();
|
CardDb db = entry.getValue();
|
||||||
paperCard = db.getCard(cardName, setCode, artIndex, colorID);
|
paperCard = db.getCard(cardName, setCode, artIndex, flags);
|
||||||
if (paperCard != null) {
|
if (paperCard != null) {
|
||||||
selectedDbName = dbName;
|
selectedDbName = dbName;
|
||||||
break;
|
break;
|
||||||
@@ -123,7 +139,7 @@ public class CardPool extends ItemPool<PaperCard> {
|
|||||||
int cnt = artGroups[i - 1];
|
int cnt = artGroups[i - 1];
|
||||||
if (cnt <= 0)
|
if (cnt <= 0)
|
||||||
continue;
|
continue;
|
||||||
PaperCard randomCard = cardDb.getCard(cardName, setCode, i, colorID);
|
PaperCard randomCard = cardDb.getCard(cardName, setCode, i, flags);
|
||||||
this.add(randomCard, cnt);
|
this.add(randomCard, cnt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -430,7 +446,6 @@ public class CardPool extends ItemPool<PaperCard> {
|
|||||||
public String toCardList(String separator) {
|
public String toCardList(String separator) {
|
||||||
List<Entry<PaperCard, Integer>> main2sort = Lists.newArrayList(this);
|
List<Entry<PaperCard, Integer>> main2sort = Lists.newArrayList(this);
|
||||||
main2sort.sort(ItemPoolSorter.BY_NAME_THEN_SET);
|
main2sort.sort(ItemPoolSorter.BY_NAME_THEN_SET);
|
||||||
final CardDb commonDb = StaticData.instance().getCommonCards();
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
boolean isFirst = true;
|
boolean isFirst = true;
|
||||||
@@ -441,10 +456,8 @@ public class CardPool extends ItemPool<PaperCard> {
|
|||||||
else
|
else
|
||||||
isFirst = false;
|
isFirst = false;
|
||||||
|
|
||||||
CardDb db = !e.getKey().getRules().isVariant() ? commonDb : StaticData.instance().getVariantCards();
|
|
||||||
sb.append(e.getValue()).append(" ");
|
sb.append(e.getValue()).append(" ");
|
||||||
db.appendCardToStringBuilder(e.getKey(), sb);
|
sb.append(CardDb.CardRequest.compose(e.getKey()));
|
||||||
|
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
@@ -463,20 +476,4 @@ public class CardPool extends ItemPool<PaperCard> {
|
|||||||
}
|
}
|
||||||
return filteredPool;
|
return filteredPool;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies a predicate to this CardPool's cards.
|
|
||||||
* @param predicate the Predicate to apply to this CardPool
|
|
||||||
* @return a new CardPool made from this CardPool with only the cards that agree with the provided Predicate
|
|
||||||
*/
|
|
||||||
public CardPool getFilteredPoolWithCardsCount(Predicate<PaperCard> predicate) {
|
|
||||||
CardPool filteredPool = new CardPool();
|
|
||||||
for (Entry<PaperCard, Integer> entry : this.items.entrySet()) {
|
|
||||||
PaperCard pc = entry.getKey();
|
|
||||||
int count = entry.getValue();
|
|
||||||
if (predicate.test(pc))
|
|
||||||
filteredPool.add(pc, count);
|
|
||||||
}
|
|
||||||
return filteredPool;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
|||||||
|
|
||||||
Map<String, List<String>> referenceDeckLoadingMap;
|
Map<String, List<String>> referenceDeckLoadingMap;
|
||||||
if (deferredSections != null) {
|
if (deferredSections != null) {
|
||||||
this.validateDeferredSections();
|
this.normalizeDeferredSections();
|
||||||
referenceDeckLoadingMap = new HashMap<>(this.deferredSections);
|
referenceDeckLoadingMap = new HashMap<>(this.deferredSections);
|
||||||
} else
|
} else
|
||||||
referenceDeckLoadingMap = new HashMap<>(loadedSections);
|
referenceDeckLoadingMap = new HashMap<>(loadedSections);
|
||||||
@@ -267,7 +267,7 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
|||||||
continue;
|
continue;
|
||||||
final List<String> cardsInSection = s.getValue();
|
final List<String> cardsInSection = s.getValue();
|
||||||
ArrayList<String> cardNamesWithNoEdition = getAllCardNamesWithNoSpecifiedEdition(cardsInSection);
|
ArrayList<String> cardNamesWithNoEdition = getAllCardNamesWithNoSpecifiedEdition(cardsInSection);
|
||||||
if (cardNamesWithNoEdition.size() > 0) {
|
if (!cardNamesWithNoEdition.isEmpty()) {
|
||||||
includeCardsFromUnspecifiedSet = true;
|
includeCardsFromUnspecifiedSet = true;
|
||||||
if (smartCardArtSelection)
|
if (smartCardArtSelection)
|
||||||
cardsWithNoEdition.put(sec, cardNamesWithNoEdition);
|
cardsWithNoEdition.put(sec, cardNamesWithNoEdition);
|
||||||
@@ -281,10 +281,10 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
|||||||
optimiseCardArtSelectionInDeckSections(cardsWithNoEdition);
|
optimiseCardArtSelectionInDeckSections(cardsWithNoEdition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateDeferredSections() {
|
private void normalizeDeferredSections() {
|
||||||
/*
|
/*
|
||||||
Construct a temporary (DeckSection, CardPool) Maps, to be sanitised and finalised
|
Construct a temporary (DeckSection, CardPool) Maps, to be sanitised and finalised
|
||||||
before copying into `this.parts`. This sanitisation is applied because of the
|
before copying into `this.parts`. This sanitization is applied because of the
|
||||||
validation schema introduced in DeckSections.
|
validation schema introduced in DeckSections.
|
||||||
*/
|
*/
|
||||||
Map<String, List<String>> validatedSections = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
Map<String, List<String>> validatedSections = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||||
@@ -296,61 +296,33 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
|||||||
}
|
}
|
||||||
|
|
||||||
final List<String> cardsInSection = s.getValue();
|
final List<String> cardsInSection = s.getValue();
|
||||||
List<Pair<String, Integer>> originalCardRequests = CardPool.processCardList(cardsInSection);
|
|
||||||
CardPool pool = CardPool.fromCardList(cardsInSection);
|
CardPool pool = CardPool.fromCardList(cardsInSection);
|
||||||
if (pool.countDistinct() == 0)
|
if (pool.countDistinct() == 0)
|
||||||
continue; // pool empty, no card has been found!
|
continue; // pool empty, no card has been found!
|
||||||
|
|
||||||
// Filter pool by applying DeckSection Validation schema for Card Types (to avoid inconsistencies)
|
List<String> validatedSection = validatedSections.computeIfAbsent(s.getKey(), (k) -> new ArrayList<>());
|
||||||
CardPool filteredPool = pool.getFilteredPoolWithCardsCount(deckSection::validate);
|
for (Entry<PaperCard, Integer> entry : pool) {
|
||||||
// Add all the cards from ValidPool anyway!
|
PaperCard card = entry.getKey();
|
||||||
List<String> whiteList = validatedSections.getOrDefault(s.getKey(), null);
|
String normalizedRequest = getPoolRequest(entry);
|
||||||
if (whiteList == null)
|
if(deckSection.validate(card))
|
||||||
whiteList = new ArrayList<>();
|
validatedSection.add(normalizedRequest);
|
||||||
for (Entry<PaperCard, Integer> entry : filteredPool) {
|
else {
|
||||||
String poolRequest = getPoolRequest(entry, originalCardRequests);
|
// Card was in the wrong section. Move it to the right section.
|
||||||
whiteList.add(poolRequest);
|
DeckSection cardSection = DeckSection.matchingSection(card);
|
||||||
|
assert(cardSection.validate(card)); //Card doesn't fit in the matchingSection?
|
||||||
|
List<String> sectionCardList = validatedSections.computeIfAbsent(cardSection.name(), (k) -> new ArrayList<>());
|
||||||
|
sectionCardList.add(normalizedRequest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
validatedSections.put(s.getKey(), whiteList);
|
|
||||||
|
|
||||||
if (filteredPool.countDistinct() != pool.countDistinct()) {
|
|
||||||
CardPool blackList = pool.getFilteredPoolWithCardsCount(input -> !(deckSection.validate(input)));
|
|
||||||
|
|
||||||
for (Entry<PaperCard, Integer> entry : blackList) {
|
|
||||||
DeckSection cardSection = DeckSection.matchingSection(entry.getKey());
|
|
||||||
String poolRequest = getPoolRequest(entry, originalCardRequests);
|
|
||||||
List<String> sectionCardList = validatedSections.getOrDefault(cardSection.name(), null);
|
|
||||||
if (sectionCardList == null)
|
|
||||||
sectionCardList = new ArrayList<>();
|
|
||||||
sectionCardList.add(poolRequest);
|
|
||||||
validatedSections.put(cardSection.name(), sectionCardList);
|
|
||||||
} // end for blacklist
|
|
||||||
} // end if
|
|
||||||
} // end main for on deferredSections
|
} // end main for on deferredSections
|
||||||
|
|
||||||
// Overwrite deferredSections
|
// Overwrite deferredSections
|
||||||
this.deferredSections = validatedSections;
|
this.deferredSections = validatedSections;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getPoolRequest(Entry<PaperCard, Integer> entry, List<Pair<String, Integer>> originalCardRequests) {
|
private String getPoolRequest(Entry<PaperCard, Integer> entry) {
|
||||||
PaperCard card = entry.getKey();
|
|
||||||
int amount = entry.getValue();
|
int amount = entry.getValue();
|
||||||
String poolCardRequest = CardDb.CardRequest.compose(
|
String poolCardRequest = CardDb.CardRequest.compose(entry.getKey());
|
||||||
card.isFoil() ? CardDb.CardRequest.compose(card.getName(), true) : card.getName(),
|
|
||||||
card.getEdition(), card.getArtIndex(), card.getColorID());
|
|
||||||
String originalRequestCandidate = null;
|
|
||||||
for (Pair<String, Integer> originalRequest : originalCardRequests){
|
|
||||||
String cardRequest = originalRequest.getLeft();
|
|
||||||
if (!StringUtils.startsWithIgnoreCase(poolCardRequest, cardRequest))
|
|
||||||
continue;
|
|
||||||
originalRequestCandidate = cardRequest;
|
|
||||||
int cardAmount = originalRequest.getRight();
|
|
||||||
if (amount == cardAmount)
|
|
||||||
return String.format("%d %s", cardAmount, cardRequest);
|
|
||||||
}
|
|
||||||
// This is just in case, it should never happen as we're
|
|
||||||
if (originalRequestCandidate != null)
|
|
||||||
return String.format("%d %s", amount, originalRequestCandidate);
|
|
||||||
return String.format("%d %s", amount, poolCardRequest);
|
return String.format("%d %s", amount, poolCardRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -987,7 +987,7 @@ public class DeckRecognizer {
|
|||||||
private static String getMagicColourLabel(MagicColor.Color magicColor) {
|
private static String getMagicColourLabel(MagicColor.Color magicColor) {
|
||||||
if (magicColor == null) // Multicolour
|
if (magicColor == null) // Multicolour
|
||||||
return String.format("%s {W}{U}{B}{R}{G}", getLocalisedMagicColorName("Multicolour"));
|
return String.format("%s {W}{U}{B}{R}{G}", getLocalisedMagicColorName("Multicolour"));
|
||||||
return String.format("%s %s", getLocalisedMagicColorName(magicColor.getName()), magicColor.getSymbol());
|
return String.format("%s %s", magicColor.getLocalizedName(), magicColor.getSymbol());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final HashMap<Integer, String> manaSymbolsMap = new HashMap<Integer, String>() {{
|
private static final HashMap<Integer, String> manaSymbolsMap = new HashMap<Integer, String>() {{
|
||||||
@@ -1006,8 +1006,8 @@ public class DeckRecognizer {
|
|||||||
if (magicColor2 == null || magicColor2 == MagicColor.Color.COLORLESS
|
if (magicColor2 == null || magicColor2 == MagicColor.Color.COLORLESS
|
||||||
|| magicColor1 == MagicColor.Color.COLORLESS)
|
|| magicColor1 == MagicColor.Color.COLORLESS)
|
||||||
return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2));
|
return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2));
|
||||||
String localisedName1 = getLocalisedMagicColorName(magicColor1.getName());
|
String localisedName1 = magicColor1.getLocalizedName();
|
||||||
String localisedName2 = getLocalisedMagicColorName(magicColor2.getName());
|
String localisedName2 = magicColor2.getLocalizedName();
|
||||||
String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColormask() | magicColor2.getColormask());
|
String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColormask() | magicColor2.getColormask());
|
||||||
return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol);
|
return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public enum DeckSection {
|
|||||||
CardType t = card.getRules().getType();
|
CardType t = card.getRules().getType();
|
||||||
// NOTE: Same rules applies to both Deck and Side, despite "Conspiracy cards" are allowed
|
// NOTE: Same rules applies to both Deck and Side, despite "Conspiracy cards" are allowed
|
||||||
// in the SideBoard (see Rule 313.2)
|
// in the SideBoard (see Rule 313.2)
|
||||||
// Those will be matched later, in case (see `Deck::validateDeferredSections`)
|
// Those will be matched later, in case (see `Deck::normalizeDeferredSections`)
|
||||||
return !t.isConspiracy() && !t.isDungeon() && !t.isPhenomenon() && !t.isPlane() && !t.isScheme() && !t.isVanguard();
|
return !t.isConspiracy() && !t.isDungeon() && !t.isPhenomenon() && !t.isPlane() && !t.isScheme() && !t.isVanguard();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ public class DeckSerializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for(Entry<DeckSection, CardPool> s : d) {
|
for(Entry<DeckSection, CardPool> s : d) {
|
||||||
|
if(s.getValue().isEmpty())
|
||||||
|
continue;
|
||||||
out.add(TextUtil.enclosedBracket(s.getKey().toString()));
|
out.add(TextUtil.enclosedBracket(s.getKey().toString()));
|
||||||
out.add(s.getValue().toCardList(System.lineSeparator()));
|
out.add(s.getValue().toCardList(System.lineSeparator()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package forge.item;
|
|||||||
|
|
||||||
import forge.card.CardRarity;
|
import forge.card.CardRarity;
|
||||||
import forge.card.CardRules;
|
import forge.card.CardRules;
|
||||||
|
import forge.card.ColorSet;
|
||||||
import forge.card.ICardFace;
|
import forge.card.ICardFace;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public interface IPaperCard extends InventoryItem, Serializable {
|
public interface IPaperCard extends InventoryItem, Serializable {
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ public interface IPaperCard extends InventoryItem, Serializable {
|
|||||||
String getEdition();
|
String getEdition();
|
||||||
String getCollectorNumber();
|
String getCollectorNumber();
|
||||||
String getFunctionalVariant();
|
String getFunctionalVariant();
|
||||||
Set<String> getColorID();
|
ColorSet getMarkedColors();
|
||||||
int getArtIndex();
|
int getArtIndex();
|
||||||
boolean isFoil();
|
boolean isFoil();
|
||||||
boolean isToken();
|
boolean isToken();
|
||||||
|
|||||||
@@ -24,12 +24,10 @@ import forge.util.CardTranslation;
|
|||||||
import forge.util.ImageUtil;
|
import forge.util.ImageUtil;
|
||||||
import forge.util.Localizer;
|
import forge.util.Localizer;
|
||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.*;
|
||||||
import java.io.ObjectInputStream;
|
import java.util.*;
|
||||||
import java.util.Optional;
|
import java.util.stream.Collectors;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A lightweight version of a card that matches real-world cards, to use outside of games (eg. inventory, decks, trade).
|
* A lightweight version of a card that matches real-world cards, to use outside of games (eg. inventory, decks, trade).
|
||||||
@@ -39,6 +37,7 @@ import java.util.Set;
|
|||||||
* @author Forge
|
* @author Forge
|
||||||
*/
|
*/
|
||||||
public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet, IPaperCard {
|
public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet, IPaperCard {
|
||||||
|
@Serial
|
||||||
private static final long serialVersionUID = 2942081982620691205L;
|
private static final long serialVersionUID = 2942081982620691205L;
|
||||||
|
|
||||||
// Reference to rules
|
// Reference to rules
|
||||||
@@ -55,16 +54,15 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
private String artist;
|
private String artist;
|
||||||
private final int artIndex;
|
private final int artIndex;
|
||||||
private final boolean foil;
|
private final boolean foil;
|
||||||
private Boolean hasImage;
|
private final PaperCardFlags flags;
|
||||||
private final boolean noSell;
|
private final String sortableName;
|
||||||
private Set<String> colorID;
|
|
||||||
private String sortableName;
|
|
||||||
private final String functionalVariant;
|
private final String functionalVariant;
|
||||||
|
|
||||||
// Calculated fields are below:
|
// Calculated fields are below:
|
||||||
private transient CardRarity rarity; // rarity is given in ctor when set is assigned
|
private transient CardRarity rarity; // rarity is given in ctor when set is assigned
|
||||||
// Reference to a new instance of Self, but foiled!
|
// Reference to a new instance of Self, but foiled!
|
||||||
private transient PaperCard foiledVersion, noSellVersion;
|
private transient PaperCard foiledVersion, noSellVersion, flaglessVersion;
|
||||||
|
private transient Boolean hasImage;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@@ -89,8 +87,8 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getColorID() {
|
public ColorSet getMarkedColors() {
|
||||||
return colorID;
|
return this.flags.markedColors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -147,32 +145,32 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
return unFoiledVersion;
|
return unFoiledVersion;
|
||||||
}
|
}
|
||||||
public PaperCard getNoSellVersion() {
|
public PaperCard getNoSellVersion() {
|
||||||
if (this.noSell)
|
if (this.flags.noSellValue)
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
if (this.noSellVersion == null) {
|
if (this.noSellVersion == null)
|
||||||
this.noSellVersion = new PaperCard(this.rules, this.edition, this.rarity,
|
this.noSellVersion = new PaperCard(this, this.flags.withNoSellValueFlag(true));
|
||||||
this.artIndex, this.foil, String.valueOf(collectorNumber), this.artist, this.functionalVariant, true);
|
|
||||||
}
|
|
||||||
return this.noSellVersion;
|
return this.noSellVersion;
|
||||||
}
|
}
|
||||||
public PaperCard getSellable() {
|
|
||||||
if (!this.noSell)
|
|
||||||
return this;
|
|
||||||
|
|
||||||
PaperCard sellable = new PaperCard(this.rules, this.edition, this.rarity,
|
public PaperCard copyWithoutFlags() {
|
||||||
this.artIndex, this.foil, String.valueOf(collectorNumber), this.artist, this.functionalVariant, false);
|
if(this.flaglessVersion == null) {
|
||||||
return sellable;
|
if(this.flags == PaperCardFlags.IDENTITY_FLAGS)
|
||||||
|
this.flaglessVersion = this;
|
||||||
|
else
|
||||||
|
this.flaglessVersion = new PaperCard(this, null);
|
||||||
|
}
|
||||||
|
return flaglessVersion;
|
||||||
}
|
}
|
||||||
public PaperCard getColorIDVersion(Set<String> colors) {
|
public PaperCard copyWithFlags(Map<String, String> flags) {
|
||||||
if (colors == null && this.colorID == null)
|
if(flags == null || flags.isEmpty())
|
||||||
|
return this.copyWithoutFlags();
|
||||||
|
return new PaperCard(this, new PaperCardFlags(flags));
|
||||||
|
}
|
||||||
|
public PaperCard copyWithMarkedColors(ColorSet colors) {
|
||||||
|
if(Objects.equals(colors, this.flags.markedColors))
|
||||||
return this;
|
return this;
|
||||||
if (this.colorID != null && this.colorID.equals(colors))
|
return new PaperCard(this, this.flags.withMarkedColors(colors));
|
||||||
return this;
|
|
||||||
if (colors != null && colors.equals(this.colorID))
|
|
||||||
return this;
|
|
||||||
return new PaperCard(this.rules, this.edition, this.rarity,
|
|
||||||
this.artIndex, this.foil, String.valueOf(collectorNumber), this.artist, this.functionalVariant, this.noSell, colors);
|
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public String getItemType() {
|
public String getItemType() {
|
||||||
@@ -180,8 +178,12 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
return localizer.getMessage("lblCard");
|
return localizer.getMessage("lblCard");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNoSell() {
|
public PaperCardFlags getMarkedFlags() {
|
||||||
return noSell;
|
return this.flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNoSellValue() {
|
||||||
|
return this.flags.noSellValue;
|
||||||
}
|
}
|
||||||
public boolean hasImage() {
|
public boolean hasImage() {
|
||||||
return hasImage(false);
|
return hasImage(false);
|
||||||
@@ -198,38 +200,41 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
IPaperCard.NO_COLLECTOR_NUMBER, IPaperCard.NO_ARTIST_NAME, IPaperCard.NO_FUNCTIONAL_VARIANT);
|
IPaperCard.NO_COLLECTOR_NUMBER, IPaperCard.NO_ARTIST_NAME, IPaperCard.NO_FUNCTIONAL_VARIANT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PaperCard(final PaperCard copyFrom, final PaperCardFlags flags) {
|
||||||
|
this(copyFrom.rules, copyFrom.edition, copyFrom.rarity, copyFrom.artIndex, copyFrom.foil, copyFrom.collectorNumber,
|
||||||
|
copyFrom.artist, copyFrom.functionalVariant, flags);
|
||||||
|
this.flaglessVersion = copyFrom.flaglessVersion;
|
||||||
|
}
|
||||||
|
|
||||||
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0,
|
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0,
|
||||||
final int artIndex0, final boolean foil0, final String collectorNumber0,
|
final int artIndex0, final boolean foil0, final String collectorNumber0,
|
||||||
final String artist0, final String functionalVariant) {
|
final String artist0, final String functionalVariant) {
|
||||||
this(rules0, edition0, rarity0, artIndex0, foil0, collectorNumber0, artist0, functionalVariant, false);
|
this(rules0, edition0, rarity0, artIndex0, foil0, collectorNumber0, artist0, functionalVariant, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0,
|
protected PaperCard(final CardRules rules, final String edition, final CardRarity rarity,
|
||||||
final int artIndex0, final boolean foil0, final String collectorNumber0,
|
final int artIndex, final boolean foil, final String collectorNumber,
|
||||||
final String artist0, final String functionalVariant, final boolean noSell0) {
|
final String artist, final String functionalVariant, final PaperCardFlags flags) {
|
||||||
this(rules0, edition0, rarity0, artIndex0, foil0, collectorNumber0, artist0, functionalVariant, noSell0, null);
|
if (rules == null || edition == null || rarity == null) {
|
||||||
}
|
|
||||||
|
|
||||||
public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0,
|
|
||||||
final int artIndex0, final boolean foil0, final String collectorNumber0,
|
|
||||||
final String artist0, final String functionalVariant, final boolean noSell0, final Set<String> colorID0) {
|
|
||||||
if (rules0 == null || edition0 == null || rarity0 == null) {
|
|
||||||
throw new IllegalArgumentException("Cannot create card without rules, edition or rarity");
|
throw new IllegalArgumentException("Cannot create card without rules, edition or rarity");
|
||||||
}
|
}
|
||||||
rules = rules0;
|
this.rules = rules;
|
||||||
name = rules0.getName();
|
name = rules.getName();
|
||||||
edition = edition0;
|
this.edition = edition;
|
||||||
artIndex = Math.max(artIndex0, IPaperCard.DEFAULT_ART_INDEX);
|
this.artIndex = Math.max(artIndex, IPaperCard.DEFAULT_ART_INDEX);
|
||||||
foil = foil0;
|
this.foil = foil;
|
||||||
rarity = rarity0;
|
this.rarity = rarity;
|
||||||
artist = TextUtil.normalizeText(artist0);
|
this.artist = TextUtil.normalizeText(artist);
|
||||||
collectorNumber = (collectorNumber0 != null) && (collectorNumber0.length() > 0) ? collectorNumber0 : IPaperCard.NO_COLLECTOR_NUMBER;
|
this.collectorNumber = (collectorNumber != null && !collectorNumber.isEmpty()) ? collectorNumber : IPaperCard.NO_COLLECTOR_NUMBER;
|
||||||
// If the user changes the language this will make cards sort by the old language until they restart the game.
|
// If the user changes the language this will make cards sort by the old language until they restart the game.
|
||||||
// This is a good tradeoff
|
// This is a good tradeoff
|
||||||
sortableName = TextUtil.toSortableName(CardTranslation.getTranslatedName(rules0.getName()));
|
sortableName = TextUtil.toSortableName(CardTranslation.getTranslatedName(rules.getName()));
|
||||||
this.functionalVariant = functionalVariant != null ? functionalVariant : IPaperCard.NO_FUNCTIONAL_VARIANT;
|
this.functionalVariant = functionalVariant != null ? functionalVariant : IPaperCard.NO_FUNCTIONAL_VARIANT;
|
||||||
noSell = noSell0;
|
|
||||||
colorID = colorID0;
|
if(flags == null || flags.equals(PaperCardFlags.IDENTITY_FLAGS))
|
||||||
|
this.flags = PaperCardFlags.IDENTITY_FLAGS;
|
||||||
|
else
|
||||||
|
this.flags = flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PaperCard FAKE_CARD = new PaperCard(CardRules.getUnsupportedCardNamed("Fake Card"), "Fake Edition", CardRarity.Common);
|
public static PaperCard FAKE_CARD = new PaperCard(CardRules.getUnsupportedCardNamed("Fake Card"), "Fake Edition", CardRarity.Common);
|
||||||
@@ -256,8 +261,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
}
|
}
|
||||||
if (!getCollectorNumber().equals(other.getCollectorNumber()))
|
if (!getCollectorNumber().equals(other.getCollectorNumber()))
|
||||||
return false;
|
return false;
|
||||||
// colorID can be NULL
|
if (!Objects.equals(flags, other.flags))
|
||||||
if (getColorID() != other.getColorID())
|
|
||||||
return false;
|
return false;
|
||||||
return (other.foil == foil) && (other.artIndex == artIndex);
|
return (other.foil == foil) && (other.artIndex == artIndex);
|
||||||
}
|
}
|
||||||
@@ -269,13 +273,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int code = (name.hashCode() * 11) + (edition.hashCode() * 59) +
|
return Objects.hash(name, edition, collectorNumber, artIndex, foil, flags);
|
||||||
(artIndex * 2) + (getCollectorNumber().hashCode() * 383);
|
|
||||||
final int id = Optional.ofNullable(colorID).map(Set::hashCode).orElse(0);
|
|
||||||
if (foil) {
|
|
||||||
return code + id + 1;
|
|
||||||
}
|
|
||||||
return code + id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Check
|
// FIXME: Check
|
||||||
@@ -307,7 +305,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
String collectorNumber = collectorNumber0;
|
String collectorNumber = collectorNumber0;
|
||||||
if (collectorNumber.equals(NO_COLLECTOR_NUMBER))
|
if (collectorNumber.equals(NO_COLLECTOR_NUMBER))
|
||||||
collectorNumber = null;
|
collectorNumber = null;
|
||||||
return CardEdition.CardInSet.getSortableCollectorNumber(collectorNumber);
|
return CardEdition.getSortableCollectorNumber(collectorNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String sortableCNKey = null;
|
private String sortableCNKey = null;
|
||||||
@@ -339,6 +337,7 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
return Integer.compare(artIndex, o.getArtIndex());
|
return Integer.compare(artIndex, o.getArtIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serial
|
||||||
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
|
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
|
||||||
// default deserialization
|
// default deserialization
|
||||||
ois.defaultReadObject();
|
ois.defaultReadObject();
|
||||||
@@ -354,22 +353,24 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
rarity = pc.getRarity();
|
rarity = pc.getRarity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private Object readResolve() throws ObjectStreamException {
|
||||||
|
//If we deserialize an old PaperCard with no flags, reinitialize as a fresh copy to set default flags.
|
||||||
|
if(this.flags == null)
|
||||||
|
return new PaperCard(this, null);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getImageKey(boolean altState) {
|
public String getImageKey(boolean altState) {
|
||||||
String noramlizedName = StringUtils.stripAccents(name);
|
return altState ? this.getCardAltImageKey() : this.getCardImageKey();
|
||||||
String imageKey = ImageKeys.CARD_PREFIX + noramlizedName + CardDb.NameSetSeparator
|
|
||||||
+ edition + CardDb.NameSetSeparator + artIndex;
|
|
||||||
if (altState) {
|
|
||||||
imageKey += ImageKeys.BACKFACE_POSTFIX;
|
|
||||||
}
|
|
||||||
return imageKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String cardImageKey = null;
|
private String cardImageKey = null;
|
||||||
@Override
|
@Override
|
||||||
public String getCardImageKey() {
|
public String getCardImageKey() {
|
||||||
if (this.cardImageKey == null)
|
if (this.cardImageKey == null)
|
||||||
this.cardImageKey = ImageUtil.getImageKey(this, "", true);
|
this.cardImageKey = ImageUtil.getImageKey(this, CardStateName.Original);
|
||||||
return cardImageKey;
|
return cardImageKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,9 +379,9 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
public String getCardAltImageKey() {
|
public String getCardAltImageKey() {
|
||||||
if (this.cardAltImageKey == null){
|
if (this.cardAltImageKey == null){
|
||||||
if (this.hasBackFace())
|
if (this.hasBackFace())
|
||||||
this.cardAltImageKey = ImageUtil.getImageKey(this, "back", true);
|
this.cardAltImageKey = ImageUtil.getImageKey(this, this.getRules().getSplitType().getChangedStateName());
|
||||||
else // altImageKey will be the same as cardImageKey
|
else // altImageKey will be the same as cardImageKey
|
||||||
this.cardAltImageKey = ImageUtil.getImageKey(this, "", true);
|
this.cardAltImageKey = getCardImageKey();
|
||||||
}
|
}
|
||||||
return cardAltImageKey;
|
return cardAltImageKey;
|
||||||
}
|
}
|
||||||
@@ -390,9 +391,9 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
public String getCardWSpecImageKey() {
|
public String getCardWSpecImageKey() {
|
||||||
if (this.cardWSpecImageKey == null) {
|
if (this.cardWSpecImageKey == null) {
|
||||||
if (this.rules.getSplitType() == CardSplitType.Specialize)
|
if (this.rules.getSplitType() == CardSplitType.Specialize)
|
||||||
this.cardWSpecImageKey = ImageUtil.getImageKey(this, "white", true);
|
this.cardWSpecImageKey = ImageUtil.getImageKey(this, CardStateName.SpecializeW);
|
||||||
else // just use cardImageKey
|
else // just use cardImageKey
|
||||||
this.cardWSpecImageKey = ImageUtil.getImageKey(this, "", true);
|
this.cardWSpecImageKey = getCardImageKey();
|
||||||
}
|
}
|
||||||
return cardWSpecImageKey;
|
return cardWSpecImageKey;
|
||||||
}
|
}
|
||||||
@@ -402,9 +403,9 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
public String getCardUSpecImageKey() {
|
public String getCardUSpecImageKey() {
|
||||||
if (this.cardUSpecImageKey == null) {
|
if (this.cardUSpecImageKey == null) {
|
||||||
if (this.rules.getSplitType() == CardSplitType.Specialize)
|
if (this.rules.getSplitType() == CardSplitType.Specialize)
|
||||||
this.cardUSpecImageKey = ImageUtil.getImageKey(this, "blue", true);
|
this.cardUSpecImageKey = ImageUtil.getImageKey(this, CardStateName.SpecializeU);
|
||||||
else // just use cardImageKey
|
else // just use cardImageKey
|
||||||
this.cardUSpecImageKey = ImageUtil.getImageKey(this, "", true);
|
this.cardUSpecImageKey = getCardImageKey();
|
||||||
}
|
}
|
||||||
return cardUSpecImageKey;
|
return cardUSpecImageKey;
|
||||||
}
|
}
|
||||||
@@ -414,9 +415,9 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
public String getCardBSpecImageKey() {
|
public String getCardBSpecImageKey() {
|
||||||
if (this.cardBSpecImageKey == null) {
|
if (this.cardBSpecImageKey == null) {
|
||||||
if (this.rules.getSplitType() == CardSplitType.Specialize)
|
if (this.rules.getSplitType() == CardSplitType.Specialize)
|
||||||
this.cardBSpecImageKey = ImageUtil.getImageKey(this, "black", true);
|
this.cardBSpecImageKey = ImageUtil.getImageKey(this, CardStateName.SpecializeB);
|
||||||
else // just use cardImageKey
|
else // just use cardImageKey
|
||||||
this.cardBSpecImageKey = ImageUtil.getImageKey(this, "", true);
|
this.cardBSpecImageKey = getCardImageKey();
|
||||||
}
|
}
|
||||||
return cardBSpecImageKey;
|
return cardBSpecImageKey;
|
||||||
}
|
}
|
||||||
@@ -426,9 +427,9 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
public String getCardRSpecImageKey() {
|
public String getCardRSpecImageKey() {
|
||||||
if (this.cardRSpecImageKey == null) {
|
if (this.cardRSpecImageKey == null) {
|
||||||
if (this.rules.getSplitType() == CardSplitType.Specialize)
|
if (this.rules.getSplitType() == CardSplitType.Specialize)
|
||||||
this.cardRSpecImageKey = ImageUtil.getImageKey(this, "red", true);
|
this.cardRSpecImageKey = ImageUtil.getImageKey(this, CardStateName.SpecializeR);
|
||||||
else // just use cardImageKey
|
else // just use cardImageKey
|
||||||
this.cardRSpecImageKey = ImageUtil.getImageKey(this, "", true);
|
this.cardRSpecImageKey = getCardImageKey();
|
||||||
}
|
}
|
||||||
return cardRSpecImageKey;
|
return cardRSpecImageKey;
|
||||||
}
|
}
|
||||||
@@ -438,18 +439,16 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
public String getCardGSpecImageKey() {
|
public String getCardGSpecImageKey() {
|
||||||
if (this.cardGSpecImageKey == null) {
|
if (this.cardGSpecImageKey == null) {
|
||||||
if (this.rules.getSplitType() == CardSplitType.Specialize)
|
if (this.rules.getSplitType() == CardSplitType.Specialize)
|
||||||
this.cardGSpecImageKey = ImageUtil.getImageKey(this, "green", true);
|
this.cardGSpecImageKey = ImageUtil.getImageKey(this, CardStateName.SpecializeG);
|
||||||
else // just use cardImageKey
|
else // just use cardImageKey
|
||||||
this.cardGSpecImageKey = ImageUtil.getImageKey(this, "", true);
|
this.cardGSpecImageKey = getCardImageKey();
|
||||||
}
|
}
|
||||||
return cardGSpecImageKey;
|
return cardGSpecImageKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasBackFace(){
|
public boolean hasBackFace(){
|
||||||
CardSplitType cst = this.rules.getSplitType();
|
return this.rules.hasBackSide();
|
||||||
return cst == CardSplitType.Transform || cst == CardSplitType.Flip || cst == CardSplitType.Meld
|
|
||||||
|| cst == CardSplitType.Modal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -493,4 +492,88 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
public boolean isRebalanced() {
|
public boolean isRebalanced() {
|
||||||
return StaticData.instance().isRebalanced(name);
|
return StaticData.instance().isRebalanced(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains properties of a card which distinguish it from an otherwise identical copy of the card with the same
|
||||||
|
* name, edition, and collector number. Examples include permanent markings on the card, and flags for Adventure
|
||||||
|
* mode.
|
||||||
|
*/
|
||||||
|
public static class PaperCardFlags implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = -3924720485840169336L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chosen colors, for cards like Cryptic Spires.
|
||||||
|
*/
|
||||||
|
public final ColorSet markedColors;
|
||||||
|
/**
|
||||||
|
* Removes the sell value of the card in Adventure mode.
|
||||||
|
*/
|
||||||
|
public final boolean noSellValue;
|
||||||
|
|
||||||
|
//TODO: Could probably move foil here.
|
||||||
|
|
||||||
|
static final PaperCardFlags IDENTITY_FLAGS = new PaperCardFlags(Map.of());
|
||||||
|
|
||||||
|
protected PaperCardFlags(Map<String, String> flags) {
|
||||||
|
if(flags.containsKey("markedColors"))
|
||||||
|
markedColors = ColorSet.fromNames(flags.get("markedColors").split(""));
|
||||||
|
else
|
||||||
|
markedColors = null;
|
||||||
|
noSellValue = flags.containsKey("noSellValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Copy constructor. There are some better ways to do this, and they should be explored once we have more than 4
|
||||||
|
//or 5 fields here. Just need to ensure it's impossible to accidentally change a field while the PaperCardFlags
|
||||||
|
//object is in use.
|
||||||
|
private PaperCardFlags(PaperCardFlags copyFrom, ColorSet markedColors, Boolean noSellValue) {
|
||||||
|
if(markedColors == null)
|
||||||
|
markedColors = copyFrom.markedColors;
|
||||||
|
else if(markedColors.isColorless())
|
||||||
|
markedColors = null;
|
||||||
|
this.markedColors = markedColors;
|
||||||
|
this.noSellValue = noSellValue != null ? noSellValue : copyFrom.noSellValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PaperCardFlags withMarkedColors(ColorSet markedColors) {
|
||||||
|
if(markedColors == null)
|
||||||
|
markedColors = ColorSet.getNullColor();
|
||||||
|
return new PaperCardFlags(this, markedColors, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PaperCardFlags withNoSellValueFlag(boolean noSellValue) {
|
||||||
|
return new PaperCardFlags(this, null, noSellValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> asMap;
|
||||||
|
public Map<String, String> toMap() {
|
||||||
|
if(asMap != null)
|
||||||
|
return asMap;
|
||||||
|
Map<String, String> out = new HashMap<>();
|
||||||
|
if(markedColors != null && !markedColors.isColorless())
|
||||||
|
out.put("markedColors", markedColors.toString());
|
||||||
|
if(noSellValue)
|
||||||
|
out.put("noSellValue", "true");
|
||||||
|
asMap = out;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.toMap().entrySet().stream()
|
||||||
|
.map((e) -> e.getKey() + "=" + e.getValue())
|
||||||
|
.collect(Collectors.joining("\t"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof PaperCardFlags that)) return false;
|
||||||
|
return noSellValue == that.noSellValue && Objects.equals(markedColors, that.markedColors);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(markedColors, noSellValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private String name;
|
private String name;
|
||||||
|
private String collectorNumber;
|
||||||
|
private String artist;
|
||||||
private transient CardEdition edition;
|
private transient CardEdition edition;
|
||||||
private ArrayList<String> imageFileName = new ArrayList<>();
|
private ArrayList<String> imageFileName = new ArrayList<>();
|
||||||
private transient CardRules cardRules;
|
private transient CardRules cardRules;
|
||||||
@@ -54,75 +55,31 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
|||||||
return makeTokenFileName(fileName);
|
return makeTokenFileName(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String makeTokenFileName(final CardRules rules, CardEdition edition) {
|
public PaperToken(final CardRules c, CardEdition edition0, String imageFileName, String collectorNumber, String artist) {
|
||||||
ArrayList<String> build = new ArrayList<>();
|
|
||||||
|
|
||||||
String subtypes = StringUtils.join(rules.getType().getSubtypes(), " ");
|
|
||||||
if (!rules.getName().equals(subtypes)) {
|
|
||||||
return makeTokenFileName(rules.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
ColorSet colors = rules.getColor();
|
|
||||||
|
|
||||||
if (colors.isColorless()) {
|
|
||||||
build.add("C");
|
|
||||||
} else {
|
|
||||||
String color = "";
|
|
||||||
if (colors.hasWhite()) color += "W";
|
|
||||||
if (colors.hasBlue()) color += "U";
|
|
||||||
if (colors.hasBlack()) color += "B";
|
|
||||||
if (colors.hasRed()) color += "R";
|
|
||||||
if (colors.hasGreen()) color += "G";
|
|
||||||
|
|
||||||
build.add(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rules.getPower() != null && rules.getToughness() != null) {
|
|
||||||
build.add(rules.getPower());
|
|
||||||
build.add(rules.getToughness());
|
|
||||||
}
|
|
||||||
|
|
||||||
String cardTypes = "";
|
|
||||||
if (rules.getType().isArtifact()) cardTypes += "A";
|
|
||||||
if (rules.getType().isEnchantment()) cardTypes += "E";
|
|
||||||
|
|
||||||
if (!cardTypes.isEmpty()) {
|
|
||||||
build.add(cardTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
build.add(subtypes);
|
|
||||||
|
|
||||||
// Are these keywords sorted?
|
|
||||||
for (String keyword : rules.getMainPart().getKeywords()) {
|
|
||||||
build.add(keyword);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (edition != null) {
|
|
||||||
build.add(edition.getCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
return StringUtils.join(build, "_").replace('*', 'x').toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PaperToken(final CardRules c, CardEdition edition0, String imageFileName) {
|
|
||||||
this.cardRules = c;
|
this.cardRules = c;
|
||||||
this.name = c.getName();
|
this.name = c.getName();
|
||||||
this.edition = edition0;
|
this.edition = edition0;
|
||||||
|
this.collectorNumber = collectorNumber;
|
||||||
|
this.artist = artist;
|
||||||
|
|
||||||
if (edition != null && edition.getTokens().containsKey(imageFileName)) {
|
if (collectorNumber != null && !collectorNumber.isEmpty() && edition != null && edition.getTokens().containsKey(imageFileName)) {
|
||||||
this.artIndex = edition.getTokens().get(imageFileName);
|
int idx = 0;
|
||||||
}
|
// count the one with the same collectorNumber
|
||||||
|
for (CardEdition.EditionEntry t : edition.getTokens().get(imageFileName)) {
|
||||||
if (imageFileName == null) {
|
++idx;
|
||||||
// This shouldn't really happen. We can just use the normalized name again for the base image name
|
if (!t.collectorNumber().equals(collectorNumber)) {
|
||||||
this.imageFileName.add(makeTokenFileName(c, edition0));
|
continue;
|
||||||
} else {
|
}
|
||||||
String formatEdition = null == edition || CardEdition.UNKNOWN == edition ? "" : "_" + edition.getCode().toLowerCase();
|
// TODO make better image file names when collector number is known
|
||||||
|
// for the right index, we need to count the ones with wrong collector number too
|
||||||
this.imageFileName.add(String.format("%s%s", imageFileName, formatEdition));
|
this.imageFileName.add(String.format("%s|%s|%s|%d", imageFileName, edition.getCode(), collectorNumber, idx));
|
||||||
for (int idx = 2; idx <= this.artIndex; idx++) {
|
|
||||||
this.imageFileName.add(String.format("%s%d%s", imageFileName, idx, formatEdition));
|
|
||||||
}
|
}
|
||||||
|
this.artIndex = this.imageFileName.size();
|
||||||
|
} else if (null == edition || CardEdition.UNKNOWN == edition) {
|
||||||
|
this.imageFileName.add(imageFileName);
|
||||||
|
} else {
|
||||||
|
// Fallback if CollectorNumber is not used
|
||||||
|
this.imageFileName.add(String.format("%s|%s", imageFileName, edition.getCode()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,12 +95,14 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getEdition() {
|
public String getEdition() {
|
||||||
return edition != null ? edition.getCode() : "???";
|
return edition != null ? edition.getCode() : CardEdition.UNKNOWN_CODE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCollectorNumber() {
|
public String getCollectorNumber() {
|
||||||
return IPaperCard.NO_COLLECTOR_NUMBER;
|
if (collectorNumber.isEmpty())
|
||||||
|
return IPaperCard.NO_COLLECTOR_NUMBER;
|
||||||
|
return collectorNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -153,7 +112,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getColorID() {
|
public ColorSet getMarkedColors() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,13 +137,8 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getArtist() { /*TODO*/
|
public String getArtist() {
|
||||||
return "";
|
return artist;
|
||||||
}
|
|
||||||
|
|
||||||
// Unfortunately this is a property of token, cannot move it outside of class
|
|
||||||
public String getImageFilename() {
|
|
||||||
return getImageFilename(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getImageFilename(int idx) {
|
public String getImageFilename(int idx) {
|
||||||
@@ -259,24 +213,21 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
|||||||
// InventoryItem
|
// InventoryItem
|
||||||
@Override
|
@Override
|
||||||
public String getImageKey(boolean altState) {
|
public String getImageKey(boolean altState) {
|
||||||
if (hasBackFace()) {
|
String suffix = "";
|
||||||
String edCode = edition != null ? "_" + edition.getCode().toLowerCase() : "";
|
if (hasBackFace() && altState) {
|
||||||
if (altState) {
|
if (collectorNumber != null && !collectorNumber.isEmpty() && edition != null) {
|
||||||
String name = ImageKeys.TOKEN_PREFIX + cardRules.getOtherPart().getName().toLowerCase().replace(" token", "");
|
String name = cardRules.getOtherPart().getName().toLowerCase().replace(" token", "").replace(" ", "_");
|
||||||
name.replace(" ", "_");
|
return ImageKeys.getTokenKey(String.format("%s|%s|%s%s", name, edition.getCode(), collectorNumber, ImageKeys.BACKFACE_POSTFIX));
|
||||||
return name + edCode;
|
|
||||||
} else {
|
} else {
|
||||||
String name = ImageKeys.TOKEN_PREFIX + cardRules.getMainPart().getName().toLowerCase().replace(" token", "");
|
suffix = ImageKeys.BACKFACE_POSTFIX;
|
||||||
name.replace(" ", "_");
|
|
||||||
return name + edCode;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int idx = MyRandom.getRandom().nextInt(artIndex);
|
int idx = MyRandom.getRandom().nextInt(artIndex);
|
||||||
return getImageKey(idx);
|
return getImageKey(idx) + suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getImageKey(int artIndex) {
|
public String getImageKey(int artIndex) {
|
||||||
return ImageKeys.TOKEN_PREFIX + imageFileName.get(artIndex).replace(" ", "_");
|
return ImageKeys.getTokenKey(imageFileName.get(artIndex).replace(" ", "_"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRebalanced() {
|
public boolean isRebalanced() {
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
package forge.token;
|
package forge.token;
|
||||||
|
|
||||||
|
import com.google.common.collect.HashMultimap;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
import forge.card.CardDb;
|
import forge.card.CardDb;
|
||||||
import forge.card.CardEdition;
|
import forge.card.CardEdition;
|
||||||
import forge.card.CardRules;
|
import forge.card.CardRules;
|
||||||
|
import forge.item.IPaperCard;
|
||||||
import forge.item.PaperToken;
|
import forge.item.PaperToken;
|
||||||
|
import forge.util.Aggregates;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
@@ -23,8 +28,8 @@ public class TokenDb implements ITokenDatabase {
|
|||||||
|
|
||||||
// The image names should be the same as the script name + _set
|
// The image names should be the same as the script name + _set
|
||||||
// If that isn't found, consider falling back to the original token
|
// If that isn't found, consider falling back to the original token
|
||||||
|
private final Multimap<String, PaperToken> allTokenByName = HashMultimap.create();
|
||||||
private final Map<String, PaperToken> tokensByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
|
private final Map<String, PaperToken> extraTokensByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
|
||||||
private final CardEdition.Collection editions;
|
private final CardEdition.Collection editions;
|
||||||
private final Map<String, CardRules> rulesByName;
|
private final Map<String, CardRules> rulesByName;
|
||||||
@@ -38,38 +43,87 @@ public class TokenDb implements ITokenDatabase {
|
|||||||
return this.rulesByName.containsKey(rule);
|
return this.rulesByName.containsKey(rule);
|
||||||
|
|
||||||
}
|
}
|
||||||
@Override
|
|
||||||
public PaperToken getToken(String tokenName) {
|
|
||||||
return getToken(tokenName, CardEdition.UNKNOWN.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void preloadTokens() {
|
public void preloadTokens() {
|
||||||
for (CardEdition edition : this.editions) {
|
for (CardEdition edition : this.editions) {
|
||||||
for (String name : edition.getTokens().keySet()) {
|
for (Map.Entry<String, Collection<CardEdition.EditionEntry>> inSet : edition.getTokens().asMap().entrySet()) {
|
||||||
try {
|
String name = inSet.getKey();
|
||||||
getToken(name, edition.getCode());
|
String fullName = String.format("%s_%s", name, edition.getCode().toLowerCase());
|
||||||
} catch(Exception e) {
|
for (CardEdition.EditionEntry t : inSet.getValue()) {
|
||||||
System.out.println(name + "_" + edition.getCode() + " defined in Edition file, but not defined as a token script.");
|
allTokenByName.put(fullName, addTokenInSet(edition, name, t));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean loadTokenFromSet(CardEdition edition, String name) {
|
||||||
|
String fullName = String.format("%s_%s", name, edition.getCode().toLowerCase());
|
||||||
|
if (allTokenByName.containsKey(fullName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!edition.getTokens().containsKey(name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (CardEdition.EditionEntry t : edition.getTokens().get(name)) {
|
||||||
|
allTokenByName.put(fullName, addTokenInSet(edition, name, t));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PaperToken addTokenInSet(CardEdition edition, String name, CardEdition.EditionEntry t) {
|
||||||
|
CardRules rules;
|
||||||
|
if (rulesByName.containsKey(name)) {
|
||||||
|
rules = rulesByName.get(name);
|
||||||
|
} else if ("w_2_2_spirit".equals(name) || "w_3_3_spirit".equals(name)) { // Hotfix for Endure Token
|
||||||
|
rules = rulesByName.get("w_x_x_spirit");
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("wrong token name:" + name);
|
||||||
|
}
|
||||||
|
return new PaperToken(rules, edition, name, t.collectorNumber(), t.artistName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// try all editions to find token
|
||||||
|
protected PaperToken fallbackToken(String name) {
|
||||||
|
for (CardEdition edition : this.editions) {
|
||||||
|
String fullName = String.format("%s_%s", name, edition.getCode().toLowerCase());
|
||||||
|
if (loadTokenFromSet(edition, name)) {
|
||||||
|
return Aggregates.random(allTokenByName.get(fullName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaperToken getToken(String tokenName) {
|
||||||
|
return getToken(tokenName, CardEdition.UNKNOWN.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PaperToken getToken(String tokenName, String edition) {
|
public PaperToken getToken(String tokenName, String edition) {
|
||||||
String fullName = String.format("%s_%s", tokenName, edition.toLowerCase());
|
CardEdition realEdition = editions.getEditionByCodeOrThrow(edition);
|
||||||
|
String fullName = String.format("%s_%s", tokenName, realEdition.getCode().toLowerCase());
|
||||||
|
|
||||||
if (!tokensByName.containsKey(fullName)) {
|
// token exist in Set, return one at random
|
||||||
|
if (loadTokenFromSet(realEdition, tokenName)) {
|
||||||
|
return Aggregates.random(allTokenByName.get(fullName));
|
||||||
|
}
|
||||||
|
PaperToken fallback = this.fallbackToken(tokenName);
|
||||||
|
if (fallback != null) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!extraTokensByName.containsKey(fullName)) {
|
||||||
try {
|
try {
|
||||||
PaperToken pt = new PaperToken(rulesByName.get(tokenName), editions.get(edition), tokenName);
|
PaperToken pt = new PaperToken(rulesByName.get(tokenName), realEdition, tokenName, "", IPaperCard.NO_ARTIST_NAME);
|
||||||
tokensByName.put(fullName, pt);
|
extraTokensByName.put(fullName, pt);
|
||||||
return pt;
|
return pt;
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tokensByName.get(fullName);
|
return extraTokensByName.get(fullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -119,7 +173,7 @@ public class TokenDb implements ITokenDatabase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PaperToken> getAllTokens() {
|
public List<PaperToken> getAllTokens() {
|
||||||
return new ArrayList<>(tokensByName.values());
|
return new ArrayList<>(allTokenByName.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -139,6 +193,6 @@ public class TokenDb implements ITokenDatabase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<PaperToken> iterator() {
|
public Iterator<PaperToken> iterator() {
|
||||||
return tokensByName.values().iterator();
|
return allTokenByName.values().iterator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import forge.StaticData;
|
|||||||
import forge.card.CardDb;
|
import forge.card.CardDb;
|
||||||
import forge.card.CardRules;
|
import forge.card.CardRules;
|
||||||
import forge.card.CardSplitType;
|
import forge.card.CardSplitType;
|
||||||
|
import forge.card.CardStateName;
|
||||||
import forge.item.IPaperCard;
|
import forge.item.IPaperCard;
|
||||||
import forge.item.PaperCard;
|
import forge.item.PaperCard;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -24,20 +25,17 @@ public class ImageUtil {
|
|||||||
key = imageKey.substring(ImageKeys.CARD_PREFIX.length());
|
key = imageKey.substring(ImageKeys.CARD_PREFIX.length());
|
||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
if (key.endsWith(ImageKeys.BACKFACE_POSTFIX)) {
|
||||||
|
key = key.substring(0, key.length() - ImageKeys.BACKFACE_POSTFIX.length());
|
||||||
|
}
|
||||||
|
|
||||||
if (key.isEmpty())
|
if (key.isEmpty())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
CardDb db = StaticData.instance().getCommonCards();
|
String[] tempdata = key.split("\\|");
|
||||||
PaperCard cp = null;
|
PaperCard cp = StaticData.instance().fetchCard(tempdata[0], tempdata[1], tempdata[2]);
|
||||||
//db shouldn't be null
|
|
||||||
if (db != null) {
|
|
||||||
cp = db.getCard(key);
|
|
||||||
if (cp == null) {
|
|
||||||
db = StaticData.instance().getVariantCards();
|
|
||||||
if (db != null)
|
|
||||||
cp = db.getCard(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cp == null)
|
if (cp == null)
|
||||||
System.err.println("Can't find PaperCard from key: " + key);
|
System.err.println("Can't find PaperCard from key: " + key);
|
||||||
// return cp regardless if it's null
|
// return cp regardless if it's null
|
||||||
@@ -54,6 +52,21 @@ public class ImageUtil {
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getImageRelativePath(String name, String set, String collectorNumber, boolean artChop) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append(set).append("/");
|
||||||
|
if (!collectorNumber.isEmpty() && !collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER)) {
|
||||||
|
sb.append(collectorNumber).append("_");
|
||||||
|
}
|
||||||
|
sb.append(StringUtils.stripAccents(name));
|
||||||
|
|
||||||
|
sb.append(artChop ? ".artcrop" : ".fullborder");
|
||||||
|
sb.append(".jpg");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static String getImageRelativePath(PaperCard cp, String face, boolean includeSet, boolean isDownloadUrl) {
|
public static String getImageRelativePath(PaperCard cp, String face, boolean includeSet, boolean isDownloadUrl) {
|
||||||
final String nameToUse = cp == null ? null : getNameToUse(cp, face);
|
final String nameToUse = cp == null ? null : getNameToUse(cp, face);
|
||||||
if (nameToUse == null) {
|
if (nameToUse == null) {
|
||||||
@@ -123,25 +136,15 @@ public class ImageUtil {
|
|||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
} else if (face.equals("white")) {
|
} else if (face.equals("white")) {
|
||||||
if (card.getWSpecialize() != null) {
|
return card.getImageName(CardStateName.SpecializeW);
|
||||||
return card.getWSpecialize().getName();
|
|
||||||
}
|
|
||||||
} else if (face.equals("blue")) {
|
} else if (face.equals("blue")) {
|
||||||
if (card.getUSpecialize() != null) {
|
return card.getImageName(CardStateName.SpecializeU);
|
||||||
return card.getUSpecialize().getName();
|
|
||||||
}
|
|
||||||
} else if (face.equals("black")) {
|
} else if (face.equals("black")) {
|
||||||
if (card.getBSpecialize() != null) {
|
return card.getImageName(CardStateName.SpecializeB);
|
||||||
return card.getBSpecialize().getName();
|
|
||||||
}
|
|
||||||
} else if (face.equals("red")) {
|
} else if (face.equals("red")) {
|
||||||
if (card.getRSpecialize() != null) {
|
return card.getImageName(CardStateName.SpecializeR);
|
||||||
return card.getRSpecialize().getName();
|
|
||||||
}
|
|
||||||
} else if (face.equals("green")) {
|
} else if (face.equals("green")) {
|
||||||
if (card.getGSpecialize() != null) {
|
return card.getImageName(CardStateName.SpecializeG);
|
||||||
return card.getGSpecialize().getName();
|
|
||||||
}
|
|
||||||
} else if (CardSplitType.Split == cp.getRules().getSplitType()) {
|
} else if (CardSplitType.Split == cp.getRules().getSplitType()) {
|
||||||
return card.getMainPart().getName() + card.getOtherPart().getName();
|
return card.getMainPart().getName() + card.getOtherPart().getName();
|
||||||
} else if (!IPaperCard.NO_FUNCTIONAL_VARIANT.equals(cp.getFunctionalVariant())) {
|
} else if (!IPaperCard.NO_FUNCTIONAL_VARIANT.equals(cp.getFunctionalVariant())) {
|
||||||
@@ -150,50 +153,95 @@ public class ImageUtil {
|
|||||||
return cp.getName();
|
return cp.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getNameToUse(PaperCard cp, CardStateName face) {
|
||||||
|
if (!IPaperCard.NO_FUNCTIONAL_VARIANT.equals(cp.getFunctionalVariant())) {
|
||||||
|
return cp.getFunctionalVariant();
|
||||||
|
}
|
||||||
|
final CardRules card = cp.getRules();
|
||||||
|
return card.getImageName(face);
|
||||||
|
}
|
||||||
|
|
||||||
public static String getImageKey(PaperCard cp, String face, boolean includeSet) {
|
public static String getImageKey(PaperCard cp, String face, boolean includeSet) {
|
||||||
return getImageRelativePath(cp, face, includeSet, false);
|
return getImageRelativePath(cp, face, includeSet, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getImageKey(PaperCard cp, CardStateName face) {
|
||||||
|
String name = getNameToUse(cp, face);
|
||||||
|
String number = cp.getCollectorNumber();
|
||||||
|
String suffix = "";
|
||||||
|
switch (face) {
|
||||||
|
case SpecializeB:
|
||||||
|
number += "b";
|
||||||
|
break;
|
||||||
|
case SpecializeG:
|
||||||
|
number += "g";
|
||||||
|
break;
|
||||||
|
case SpecializeR:
|
||||||
|
number += "r";
|
||||||
|
break;
|
||||||
|
case SpecializeU:
|
||||||
|
number += "u";
|
||||||
|
break;
|
||||||
|
case SpecializeW:
|
||||||
|
number += "w";
|
||||||
|
break;
|
||||||
|
case Meld:
|
||||||
|
case Modal:
|
||||||
|
case Secondary:
|
||||||
|
case Transformed:
|
||||||
|
suffix = ImageKeys.BACKFACE_POSTFIX;
|
||||||
|
break;
|
||||||
|
case Flipped:
|
||||||
|
break; // add info to rotate the image?
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
return ImageKeys.CARD_PREFIX + name + CardDb.NameSetSeparator + cp.getEdition()
|
||||||
|
+ CardDb.NameSetSeparator + number + CardDb.NameSetSeparator + cp.getArtIndex() + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getDownloadUrl(PaperCard cp, String face) {
|
public static String getDownloadUrl(PaperCard cp, String face) {
|
||||||
return getImageRelativePath(cp, face, true, true);
|
return getImageRelativePath(cp, face, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getScryfallDownloadUrl(PaperCard cp, String face, String setCode, String langCode, boolean useArtCrop){
|
public static String getScryfallDownloadUrl(String collectorNumber, String setCode, String langCode, String faceParam, boolean useArtCrop){
|
||||||
return getScryfallDownloadUrl(cp, face, setCode, langCode, useArtCrop, false);
|
return getScryfallDownloadUrl(collectorNumber, setCode, langCode, faceParam, useArtCrop, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getScryfallDownloadUrl(PaperCard cp, String face, String setCode, String langCode, boolean useArtCrop, boolean hyphenateAlchemy){
|
public static String getScryfallDownloadUrl(String collectorNumber, String setCode, String langCode, String faceParam, boolean useArtCrop, boolean hyphenateAlchemy){
|
||||||
String editionCode;
|
|
||||||
if ((setCode != null) && (setCode.length() > 0))
|
|
||||||
editionCode = setCode;
|
|
||||||
else
|
|
||||||
editionCode = cp.getEdition().toLowerCase();
|
|
||||||
String cardCollectorNumber = cp.getCollectorNumber();
|
|
||||||
// Hack to account for variations in Arabian Nights
|
// Hack to account for variations in Arabian Nights
|
||||||
cardCollectorNumber = cardCollectorNumber.replace("+", "†");
|
collectorNumber = collectorNumber.replace("+", "†");
|
||||||
// override old planechase sets from their modified id since scryfall move the planechase cards outside their original setcode
|
// override old planechase sets from their modified id since scryfall move the planechase cards outside their original setcode
|
||||||
if (cardCollectorNumber.startsWith("OHOP")) {
|
if (collectorNumber.startsWith("OHOP")) {
|
||||||
editionCode = "ohop";
|
setCode = "ohop";
|
||||||
cardCollectorNumber = cardCollectorNumber.substring("OHOP".length());
|
collectorNumber = collectorNumber.substring("OHOP".length());
|
||||||
} else if (cardCollectorNumber.startsWith("OPCA")) {
|
} else if (collectorNumber.startsWith("OPCA")) {
|
||||||
editionCode = "opca";
|
setCode = "opca";
|
||||||
cardCollectorNumber = cardCollectorNumber.substring("OPCA".length());
|
collectorNumber = collectorNumber.substring("OPCA".length());
|
||||||
} else if (cardCollectorNumber.startsWith("OPC2")) {
|
} else if (collectorNumber.startsWith("OPC2")) {
|
||||||
editionCode = "opc2";
|
setCode = "opc2";
|
||||||
cardCollectorNumber = cardCollectorNumber.substring("OPC2".length());
|
collectorNumber = collectorNumber.substring("OPC2".length());
|
||||||
} else if (hyphenateAlchemy) {
|
} else if (hyphenateAlchemy) {
|
||||||
if (!cardCollectorNumber.startsWith("A")) {
|
if (!collectorNumber.startsWith("A")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
cardCollectorNumber = cardCollectorNumber.replace("A", "A-");
|
collectorNumber = collectorNumber.replace("A", "A-");
|
||||||
}
|
}
|
||||||
String versionParam = useArtCrop ? "art_crop" : "normal";
|
String versionParam = useArtCrop ? "art_crop" : "normal";
|
||||||
String faceParam = "";
|
if (!faceParam.isEmpty()) {
|
||||||
if (cp.getRules().getOtherPart() != null) {
|
faceParam = (faceParam.equals("back") ? "&face=back" : "&face=front");
|
||||||
faceParam = (face.equals("back") ? "&face=back" : "&face=front");
|
|
||||||
}
|
}
|
||||||
return String.format("%s/%s/%s?format=image&version=%s%s", editionCode, cardCollectorNumber,
|
return String.format("%s/%s/%s?format=image&version=%s%s", setCode, collectorNumber,
|
||||||
|
langCode, versionParam, faceParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getScryfallTokenDownloadUrl(String collectorNumber, String setCode, String langCode, String faceParam) {
|
||||||
|
String versionParam = "normal";
|
||||||
|
if (!faceParam.isEmpty()) {
|
||||||
|
faceParam = (faceParam.equals("back") ? "&face=back" : "&face=front");
|
||||||
|
}
|
||||||
|
return String.format("%s/%s/%s?format=image&version=%s%s", setCode, collectorNumber,
|
||||||
langCode, versionParam, faceParam);
|
langCode, versionParam, faceParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -269,6 +269,13 @@ public class ItemPool<T extends InventoryItem> implements Iterable<Entry<T, Inte
|
|||||||
// need not set out-of-sync: either remove did set, or nothing was removed
|
// need not set out-of-sync: either remove did set, or nothing was removed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeIf(Predicate<T> test) {
|
||||||
|
for (final T item : items.keySet()) {
|
||||||
|
if (test.test(item))
|
||||||
|
remove(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
items.clear();
|
items.clear();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ package forge.game;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.card.CardStateName;
|
import forge.card.CardStateName;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
@@ -564,13 +566,23 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
|||||||
return CardView.get(hostCard);
|
return CardView.get(hostCard);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IHasSVars getSVarFallback() {
|
protected List<IHasSVars> getSVarFallback(final String name) {
|
||||||
|
List<IHasSVars> result = Lists.newArrayList();
|
||||||
|
|
||||||
if (this.getKeyword() != null && this.getKeyword().getStatic() != null) {
|
if (this.getKeyword() != null && this.getKeyword().getStatic() != null) {
|
||||||
return this.getKeyword().getStatic();
|
// only do when the keyword has part of the SVar in ins original string
|
||||||
|
if (name == null || this.getKeyword().getOriginal().contains(name)) {
|
||||||
|
// TODO try to add the keyword instead if possible?
|
||||||
|
result.add(this.getKeyword().getStatic());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (getCardState() != null)
|
if (getCardState() != null)
|
||||||
return getCardState();
|
result.add(getCardState());
|
||||||
return getHostCard();
|
result.add(getHostCard());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
protected Optional<IHasSVars> findSVar(final String name) {
|
||||||
|
return getSVarFallback(name).stream().filter(f -> f.hasSVar(name)).findFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -578,12 +590,12 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
|||||||
if (sVars.containsKey(name)) {
|
if (sVars.containsKey(name)) {
|
||||||
return sVars.get(name);
|
return sVars.get(name);
|
||||||
}
|
}
|
||||||
return getSVarFallback().getSVar(name);
|
return findSVar(name).map(o -> o.getSVar(name)).orElse("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasSVar(final String name) {
|
public boolean hasSVar(final String name) {
|
||||||
return sVars.containsKey(name) || getSVarFallback().hasSVar(name);
|
return sVars.containsKey(name) || findSVar(name).isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getSVarInt(final String name) {
|
public Integer getSVarInt(final String name) {
|
||||||
@@ -598,22 +610,21 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void setSVar(final String name, final String value) {
|
public void setSVar(final String name, final String value) {
|
||||||
sVars.put(name, value);
|
sVars.put(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> getSVars() {
|
public Map<String, String> getSVars() {
|
||||||
Map<String, String> res = Maps.newHashMap(getSVarFallback().getSVars());
|
Map<String, String> res = Maps.newHashMap();
|
||||||
|
// TODO reverse the order
|
||||||
|
for (IHasSVars s : getSVarFallback(null)) {
|
||||||
|
res.putAll(s.getSVars());
|
||||||
|
}
|
||||||
res.putAll(sVars);
|
res.putAll(sVars);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String> getDirectSVars() {
|
|
||||||
return sVars;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSVars(Map<String, String> newSVars) {
|
public void setSVars(Map<String, String> newSVars) {
|
||||||
sVars = Maps.newTreeMap();
|
sVars = Maps.newTreeMap();
|
||||||
|
|||||||
@@ -122,30 +122,10 @@ public class ForgeScript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return found;
|
return found;
|
||||||
} else if (property.equals("hasActivatedAbilityWithTapCost")) {
|
} else if (property.startsWith("hasAbility")) {
|
||||||
|
String valid = property.substring(11);
|
||||||
for (final SpellAbility sa : cardState.getSpellAbilities()) {
|
for (final SpellAbility sa : cardState.getSpellAbilities()) {
|
||||||
if (sa.isActivatedAbility() && sa.getPayCosts().hasTapCost()) {
|
if (sa.isValid(valid, sourceController, source, spellAbility)) {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else if (property.equals("hasActivatedAbilityWithExhaust")) {
|
|
||||||
for (final SpellAbility sa : cardState.getSpellAbilities()) {
|
|
||||||
if (sa.isActivatedAbility() && sa.hasParam("Exhaust")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else if (property.equals("hasActivatedAbility")) {
|
|
||||||
for (final SpellAbility sa : cardState.getSpellAbilities()) {
|
|
||||||
if (sa.isActivatedAbility()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else if (property.equals("hasOtherActivatedAbility")) {
|
|
||||||
for (final SpellAbility sa : cardState.getSpellAbilities()) {
|
|
||||||
if (sa.isActivatedAbility() && !sa.equals(spellAbility)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,6 +205,8 @@ public class ForgeScript {
|
|||||||
return sa.isEternalize();
|
return sa.isEternalize();
|
||||||
} else if (property.equals("Flashback")) {
|
} else if (property.equals("Flashback")) {
|
||||||
return sa.isFlashback();
|
return sa.isFlashback();
|
||||||
|
} else if (property.equals("Harmonize")) {
|
||||||
|
return sa.isHarmonize();
|
||||||
} else if (property.equals("Jumpstart")) {
|
} else if (property.equals("Jumpstart")) {
|
||||||
return sa.isJumpstart();
|
return sa.isJumpstart();
|
||||||
} else if (property.equals("Kicked")) {
|
} else if (property.equals("Kicked")) {
|
||||||
@@ -243,6 +225,8 @@ public class ForgeScript {
|
|||||||
return sa.isTurnFaceUp();
|
return sa.isTurnFaceUp();
|
||||||
} else if (property.equals("isCastFaceDown")) {
|
} else if (property.equals("isCastFaceDown")) {
|
||||||
return sa.isCastFaceDown();
|
return sa.isCastFaceDown();
|
||||||
|
} else if (property.equals("Unearth")) {
|
||||||
|
return sa.isKeyword(Keyword.UNEARTH);
|
||||||
} else if (property.equals("Modular")) {
|
} else if (property.equals("Modular")) {
|
||||||
return sa.isKeyword(Keyword.MODULAR);
|
return sa.isKeyword(Keyword.MODULAR);
|
||||||
} else if (property.equals("Equip")) {
|
} else if (property.equals("Equip")) {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import com.google.common.collect.HashBasedTable;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import com.google.common.collect.Table;
|
import com.google.common.collect.Table;
|
||||||
import com.google.common.eventbus.EventBus;
|
import com.google.common.eventbus.EventBus;
|
||||||
import forge.GameCommand;
|
import forge.GameCommand;
|
||||||
@@ -957,9 +958,9 @@ public class Game {
|
|||||||
// if the player who lost was the Monarch, someone else will be the monarch
|
// if the player who lost was the Monarch, someone else will be the monarch
|
||||||
// TODO need to check rules if it should try the next player if able
|
// TODO need to check rules if it should try the next player if able
|
||||||
if (p.equals(getPhaseHandler().getPlayerTurn())) {
|
if (p.equals(getPhaseHandler().getPlayerTurn())) {
|
||||||
getAction().becomeMonarch(getNextPlayerAfter(p), null);
|
getAction().becomeMonarch(getNextPlayerAfter(p), p.getMonarchSet());
|
||||||
} else {
|
} else {
|
||||||
getAction().becomeMonarch(getPhaseHandler().getPlayerTurn(), null);
|
getAction().becomeMonarch(getPhaseHandler().getPlayerTurn(), p.getMonarchSet());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -969,9 +970,9 @@ public class Game {
|
|||||||
// If the player who has the initiative leaves the game on their own turn,
|
// 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.
|
// 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())) {
|
if (p.equals(getPhaseHandler().getPlayerTurn())) {
|
||||||
getAction().takeInitiative(getNextPlayerAfter(p), null);
|
getAction().takeInitiative(getNextPlayerAfter(p), p.getInitiativeSet());
|
||||||
} else {
|
} else {
|
||||||
getAction().takeInitiative(getPhaseHandler().getPlayerTurn(), null);
|
getAction().takeInitiative(getPhaseHandler().getPlayerTurn(), p.getInitiativeSet());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1206,29 +1207,43 @@ public class Game {
|
|||||||
|
|
||||||
public int getCounterAddedThisTurn(CounterType cType, String validPlayer, String validCard, Card source, Player sourceController, CardTraitBase ctb) {
|
public int getCounterAddedThisTurn(CounterType cType, String validPlayer, String validCard, Card source, Player sourceController, CardTraitBase ctb) {
|
||||||
int result = 0;
|
int result = 0;
|
||||||
if (!countersAddedThisTurn.containsRow(cType)) {
|
Set<CounterType> types = null;
|
||||||
|
if (cType == null) {
|
||||||
|
types = countersAddedThisTurn.rowKeySet();
|
||||||
|
} else if (!countersAddedThisTurn.containsRow(cType)) {
|
||||||
return result;
|
return result;
|
||||||
|
} else {
|
||||||
|
types = Sets.newHashSet(cType);
|
||||||
}
|
}
|
||||||
for (Map.Entry<Player, List<Pair<Card, Integer>>> e : countersAddedThisTurn.row(cType).entrySet()) {
|
for (CounterType type : types) {
|
||||||
if (e.getKey().isValid(validPlayer.split(","), sourceController, source, ctb)) {
|
for (Map.Entry<Player, List<Pair<Card, Integer>>> e : countersAddedThisTurn.row(type).entrySet()) {
|
||||||
for (Pair<Card, Integer> p : e.getValue()) {
|
if (e.getKey().isValid(validPlayer.split(","), sourceController, source, ctb)) {
|
||||||
if (p.getKey().isValid(validCard.split(","), sourceController, source, ctb)) {
|
for (Pair<Card, Integer> p : e.getValue()) {
|
||||||
result += p.getValue();
|
if (p.getKey().isValid(validCard.split(","), sourceController, source, ctb)) {
|
||||||
}
|
result += p.getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
public int getCounterAddedThisTurn(CounterType cType, Card card) {
|
public int getCounterAddedThisTurn(CounterType cType, Card card) {
|
||||||
int result = 0;
|
int result = 0;
|
||||||
if (!countersAddedThisTurn.containsRow(cType)) {
|
Set<CounterType> types = null;
|
||||||
|
if (cType == null) {
|
||||||
|
types = countersAddedThisTurn.rowKeySet();
|
||||||
|
} else if (!countersAddedThisTurn.containsRow(cType)) {
|
||||||
return result;
|
return result;
|
||||||
|
} else {
|
||||||
|
types = Sets.newHashSet(cType);
|
||||||
}
|
}
|
||||||
for (List<Pair<Card, Integer>> l : countersAddedThisTurn.row(cType).values()) {
|
for (CounterType type : types) {
|
||||||
for (Pair<Card, Integer> p : l) {
|
for (List<Pair<Card, Integer>> l : countersAddedThisTurn.row(type).values()) {
|
||||||
if (p.getKey().equalsWithGameTimestamp(card)) {
|
for (Pair<Card, Integer> p : l) {
|
||||||
result += p.getValue();
|
if (p.getKey().equalsWithGameTimestamp(card)) {
|
||||||
|
result += p.getValue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import forge.GameCommand;
|
|||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
import forge.card.CardStateName;
|
import forge.card.CardStateName;
|
||||||
import forge.card.CardType.Supertype;
|
import forge.card.CardType.Supertype;
|
||||||
|
import forge.card.ColorSet;
|
||||||
import forge.card.GamePieceType;
|
import forge.card.GamePieceType;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.deck.DeckSection;
|
import forge.deck.DeckSection;
|
||||||
@@ -43,11 +44,10 @@ import forge.game.replacement.ReplacementType;
|
|||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.SpellAbilityPredicates;
|
import forge.game.spellability.SpellAbilityPredicates;
|
||||||
import forge.game.spellability.SpellPermanent;
|
import forge.game.spellability.SpellPermanent;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.staticability.StaticAbilityCantAttackBlock;
|
|
||||||
import forge.game.staticability.StaticAbilityContinuous;
|
import forge.game.staticability.StaticAbilityContinuous;
|
||||||
import forge.game.staticability.StaticAbilityLayer;
|
import forge.game.staticability.StaticAbilityLayer;
|
||||||
|
import forge.game.staticability.StaticAbilityMode;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.PlayerZone;
|
import forge.game.zone.PlayerZone;
|
||||||
import forge.game.zone.PlayerZoneBattlefield;
|
import forge.game.zone.PlayerZoneBattlefield;
|
||||||
@@ -386,7 +386,7 @@ public class GameAction {
|
|||||||
return moveToGraveyard(copied, cause, params);
|
return moveToGraveyard(copied, cause, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
attachAuraOnIndirectEnterBattlefield(copied, params);
|
attachAuraOnIndirectETB(copied, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle merged permanent here so all replacement effects are already applied.
|
// Handle merged permanent here so all replacement effects are already applied.
|
||||||
@@ -542,8 +542,8 @@ public class GameAction {
|
|||||||
game.addLeftGraveyardThisTurn(lastKnownInfo);
|
game.addLeftGraveyardThisTurn(lastKnownInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c.hasChosenColorSpire()) {
|
if (c.hasMarkedColor()) {
|
||||||
copied.setChosenColorID(ImmutableSet.copyOf(c.getChosenColorID()));
|
copied.setMarkedColors(c.getMarkedColors());
|
||||||
}
|
}
|
||||||
|
|
||||||
copied.updateStateForView();
|
copied.updateStateForView();
|
||||||
@@ -797,7 +797,7 @@ public class GameAction {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stAb.checkMode("CantBlockBy")) {
|
if (stAb.checkMode(StaticAbilityMode.CantBlockBy)) {
|
||||||
if (!stAb.hasParam("ValidAttacker") || (stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) {
|
if (!stAb.hasParam("ValidAttacker") || (stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -807,7 +807,7 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (stAb.checkMode(StaticAbilityCantAttackBlock.MinMaxBlockerMode)) {
|
if (stAb.checkMode(StaticAbilityMode.MinMaxBlocker)) {
|
||||||
for (Card creature : IterableUtil.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.CREATURES)) {
|
for (Card creature : IterableUtil.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.CREATURES)) {
|
||||||
if (stAb.matchesValidParam("ValidCard", creature)) {
|
if (stAb.matchesValidParam("ValidCard", creature)) {
|
||||||
creature.updateAbilityTextForView();
|
creature.updateAbilityTextForView();
|
||||||
@@ -1073,7 +1073,7 @@ public class GameAction {
|
|||||||
public boolean hasStaticAbilityAffectingZone(ZoneType zone, StaticAbilityLayer layer) {
|
public boolean hasStaticAbilityAffectingZone(ZoneType zone, StaticAbilityLayer layer) {
|
||||||
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
|
||||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||||
if (!stAb.checkConditions("Continuous")) {
|
if (!stAb.checkConditions(StaticAbilityMode.Continuous)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (layer != null && !stAb.getLayers().contains(layer)) {
|
if (layer != null && !stAb.getLayers().contains(layer)) {
|
||||||
@@ -1105,10 +1105,6 @@ public class GameAction {
|
|||||||
// remove old effects
|
// remove old effects
|
||||||
game.getStaticEffects().clearStaticEffects(affectedCards);
|
game.getStaticEffects().clearStaticEffects(affectedCards);
|
||||||
|
|
||||||
for (final Player p : game.getPlayers()) {
|
|
||||||
p.clearStaticAbilities();
|
|
||||||
}
|
|
||||||
|
|
||||||
// search for cards with static abilities
|
// search for cards with static abilities
|
||||||
final FCollection<StaticAbility> staticAbilities = new FCollection<>();
|
final FCollection<StaticAbility> staticAbilities = new FCollection<>();
|
||||||
final CardCollection staticList = new CardCollection();
|
final CardCollection staticList = new CardCollection();
|
||||||
@@ -1123,7 +1119,7 @@ public class GameAction {
|
|||||||
// need to get Card from preList if able
|
// need to get Card from preList if able
|
||||||
final Card co = preList.get(c);
|
final Card co = preList.get(c);
|
||||||
for (StaticAbility stAb : co.getStaticAbilities()) {
|
for (StaticAbility stAb : co.getStaticAbilities()) {
|
||||||
if (stAb.checkMode("Continuous") && stAb.zonesCheck()) {
|
if (stAb.checkMode(StaticAbilityMode.Continuous) && stAb.zonesCheck()) {
|
||||||
staticAbilities.add(stAb);
|
staticAbilities.add(stAb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1170,7 +1166,7 @@ public class GameAction {
|
|||||||
if (affectedHere != null) {
|
if (affectedHere != null) {
|
||||||
for (final Card c : affectedHere) {
|
for (final Card c : affectedHere) {
|
||||||
for (final StaticAbility st2 : c.getStaticAbilities()) {
|
for (final StaticAbility st2 : c.getStaticAbilities()) {
|
||||||
if (!staticAbilities.contains(st2) && st2.checkMode("Continuous") && st2.zonesCheck()) {
|
if (!staticAbilities.contains(st2) && st2.checkMode(StaticAbilityMode.Continuous) && st2.zonesCheck()) {
|
||||||
toAdd.add(st2);
|
toAdd.add(st2);
|
||||||
st2.applyContinuousAbilityBefore(layer, preList);
|
st2.applyContinuousAbilityBefore(layer, preList);
|
||||||
}
|
}
|
||||||
@@ -1467,7 +1463,7 @@ public class GameAction {
|
|||||||
checkAgainCard |= stateBasedAction_Saga(c, sacrificeList);
|
checkAgainCard |= stateBasedAction_Saga(c, sacrificeList);
|
||||||
checkAgainCard |= stateBasedAction_Battle(c, noRegCreats);
|
checkAgainCard |= stateBasedAction_Battle(c, noRegCreats);
|
||||||
checkAgainCard |= stateBasedAction_Role(c, unAttachList);
|
checkAgainCard |= stateBasedAction_Role(c, unAttachList);
|
||||||
checkAgainCard |= stateBasedAction704_attach(c, unAttachList); // Attachment
|
checkAgainCard |= stateBasedAction704_attach(c, unAttachList);
|
||||||
checkAgainCard |= stateBasedAction_Contraption(c, noRegCreats);
|
checkAgainCard |= stateBasedAction_Contraption(c, noRegCreats);
|
||||||
|
|
||||||
checkAgainCard |= stateBasedAction704_5q(c); // annihilate +1/+1 counters with -1/-1 ones
|
checkAgainCard |= stateBasedAction704_5q(c); // annihilate +1/+1 counters with -1/-1 ones
|
||||||
@@ -1514,9 +1510,7 @@ public class GameAction {
|
|||||||
if (!spaceSculptors.isEmpty() && !spaceSculptors.contains(p)) {
|
if (!spaceSculptors.isEmpty() && !spaceSculptors.contains(p)) {
|
||||||
checkAgain |= stateBasedAction704_5u(p);
|
checkAgain |= stateBasedAction704_5u(p);
|
||||||
}
|
}
|
||||||
if (handleLegendRule(p, noRegCreats)) {
|
checkAgain |= handleLegendRule(p, noRegCreats);
|
||||||
checkAgain = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((game.getRules().hasAppliedVariant(GameType.Commander)
|
if ((game.getRules().hasAppliedVariant(GameType.Commander)
|
||||||
|| game.getRules().hasAppliedVariant(GameType.Brawl)
|
|| game.getRules().hasAppliedVariant(GameType.Brawl)
|
||||||
@@ -1535,13 +1529,12 @@ public class GameAction {
|
|||||||
checkAgain = true;
|
checkAgain = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handlePlaneswalkerRule(p, noRegCreats)) {
|
checkAgain |= handlePlaneswalkerRule(p, noRegCreats);
|
||||||
checkAgain = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (Player p : spaceSculptors) {
|
for (Player p : spaceSculptors) {
|
||||||
checkAgain |= stateBasedAction704_5u(p);
|
checkAgain |= stateBasedAction704_5u(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 704.5m World rule
|
// 704.5m World rule
|
||||||
checkAgain |= handleWorldRule(noRegCreats);
|
checkAgain |= handleWorldRule(noRegCreats);
|
||||||
|
|
||||||
@@ -1576,6 +1569,7 @@ public class GameAction {
|
|||||||
orderedSacrificeList = true;
|
orderedSacrificeList = true;
|
||||||
}
|
}
|
||||||
sacrifice(sacrificeList, null, true, mapParams);
|
sacrifice(sacrificeList, null, true, mapParams);
|
||||||
|
|
||||||
setHoldCheckingStaticAbilities(false);
|
setHoldCheckingStaticAbilities(false);
|
||||||
|
|
||||||
table.triggerChangesZoneAll(game, null);
|
table.triggerChangesZoneAll(game, null);
|
||||||
@@ -1634,7 +1628,7 @@ public class GameAction {
|
|||||||
|
|
||||||
private boolean stateBasedAction_Saga(Card c, CardCollection sacrificeList) {
|
private boolean stateBasedAction_Saga(Card c, CardCollection sacrificeList) {
|
||||||
boolean checkAgain = false;
|
boolean checkAgain = false;
|
||||||
if (!c.isSaga()) {
|
if (!c.isSaga() || !c.hasChapter()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// needs to be effect, because otherwise it might be a cost?
|
// needs to be effect, because otherwise it might be a cost?
|
||||||
@@ -1656,10 +1650,27 @@ public class GameAction {
|
|||||||
if (!c.isBattle()) {
|
if (!c.isBattle()) {
|
||||||
return checkAgain;
|
return checkAgain;
|
||||||
}
|
}
|
||||||
if (((c.getProtectingPlayer() == null || !c.getProtectingPlayer().isInGame()) &&
|
Player battleController = c.getController();
|
||||||
|
Player battleProtector = c.getProtectingPlayer();
|
||||||
|
/*
|
||||||
|
704.5w If a battle has no player in the game designated as its protector and no attacking creatures are currently
|
||||||
|
attacking that battle, that battle’s controller chooses an appropriate player to be its protector based on its
|
||||||
|
battle type. If no player can be chosen this way, the battle is put into its owner’s graveyard.
|
||||||
|
|
||||||
|
704.5x If a Siege’s controller is also its designated protector, that player chooses an opponent to become its
|
||||||
|
protector. If no player can be chosen this way, the battle is put into its owner’s graveyard.
|
||||||
|
*/
|
||||||
|
if (((battleProtector == null || !battleProtector.isInGame()) &&
|
||||||
(game.getCombat() == null || game.getCombat().getAttackersOf(c).isEmpty())) ||
|
(game.getCombat() == null || game.getCombat().getAttackersOf(c).isEmpty())) ||
|
||||||
(c.getType().hasStringType("Siege") && c.getController().equals(c.getProtectingPlayer()))) {
|
(c.getType().hasStringType("Siege") && battleController.equals(battleProtector))) {
|
||||||
Player newProtector = c.getController().getController().chooseSingleEntityForEffect(c.getController().getOpponents(), null, "Choose an opponent to protect this battle", null);
|
Player newProtector;
|
||||||
|
if (c.getType().getBattleTypes().contains("Siege"))
|
||||||
|
newProtector = battleController.getController().chooseSingleEntityForEffect(battleController.getOpponents(), new SpellAbility.EmptySa(ApiType.ChoosePlayer, c), "Choose an opponent to protect this battle", null);
|
||||||
|
else {
|
||||||
|
// Fall back to the controller. Technically should fall back to null per the above rules, but no official
|
||||||
|
// cards should use this branch. For now this better supports custom cards. May need to revise this later.
|
||||||
|
newProtector = battleController;
|
||||||
|
}
|
||||||
// seems unlikely unless range of influence gets implemented
|
// seems unlikely unless range of influence gets implemented
|
||||||
if (newProtector == null) {
|
if (newProtector == null) {
|
||||||
removeList.add(c);
|
removeList.add(c);
|
||||||
@@ -1745,12 +1756,12 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean stateBasedAction_Contraption(Card c, CardCollection removeList) {
|
private boolean stateBasedAction_Contraption(Card c, CardCollection removeList) {
|
||||||
if(!c.isContraption())
|
if (!c.isContraption())
|
||||||
return false;
|
return false;
|
||||||
int currentSprocket = c.getSprocket();
|
int currentSprocket = c.getSprocket();
|
||||||
|
|
||||||
//A contraption that is in the battlefield without being assembled is put into the graveyard or junkyard.
|
//A contraption that is in the battlefield without being assembled is put into the graveyard or junkyard.
|
||||||
if(currentSprocket == 0) {
|
if (currentSprocket == 0) {
|
||||||
removeList.add(c);
|
removeList.add(c);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1759,7 +1770,7 @@ public class GameAction {
|
|||||||
//A reassemble effect can handle that on its own. But if it changed controller due to some other effect,
|
//A reassemble effect can handle that on its own. But if it changed controller due to some other effect,
|
||||||
//we assign it here. A contraption uses sprocket -1 to signify it has been assembled previously but now needs
|
//we assign it here. A contraption uses sprocket -1 to signify it has been assembled previously but now needs
|
||||||
//a new sprocket.
|
//a new sprocket.
|
||||||
if(currentSprocket > 0 && currentSprocket <= 3)
|
if (currentSprocket > 0 && currentSprocket <= 3)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
int sprocket = c.getController().getController().chooseSprocket(c);
|
int sprocket = c.getController().getController().chooseSprocket(c);
|
||||||
@@ -2027,7 +2038,7 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleWorldRule(CardCollection noRegCreats) {
|
private boolean handleWorldRule(CardCollection noRegCreats) {
|
||||||
final List<Card> worlds = CardLists.getType(game.getCardsIn(ZoneType.Battlefield), "World");
|
final List<Card> worlds = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), c -> c.getType().hasSupertype(Supertype.World));
|
||||||
if (worlds.size() <= 1) {
|
if (worlds.size() <= 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2036,7 +2047,7 @@ public class GameAction {
|
|||||||
long ts = 0;
|
long ts = 0;
|
||||||
|
|
||||||
for (final Card crd : worlds) {
|
for (final Card crd : worlds) {
|
||||||
long crdTs = crd.getGameTimestamp();
|
long crdTs = crd.getWorldTimestamp();
|
||||||
if (crdTs > ts) {
|
if (crdTs > ts) {
|
||||||
ts = crdTs;
|
ts = crdTs;
|
||||||
toKeep.clear();
|
toKeep.clear();
|
||||||
@@ -2417,15 +2428,14 @@ public class GameAction {
|
|||||||
for (Card c : spires) {
|
for (Card c : spires) {
|
||||||
// TODO: only do this for the AI, for the player part, get the encoded color from the deck file and pass
|
// TODO: only do this for the AI, for the player part, get the encoded color from the deck file and pass
|
||||||
// it to either player or the papercard object so it feels like rule based for the player side..
|
// it to either player or the papercard object so it feels like rule based for the player side..
|
||||||
if (!c.hasChosenColorSpire()) {
|
if (!c.hasMarkedColor()) {
|
||||||
if (takesAction.isAI()) {
|
if (takesAction.isAI()) {
|
||||||
List<String> colorChoices = new ArrayList<>(MagicColor.Constant.ONLY_COLORS);
|
|
||||||
String prompt = CardTranslation.getTranslatedName(c.getName()) + ": " +
|
String prompt = CardTranslation.getTranslatedName(c.getName()) + ": " +
|
||||||
Localizer.getInstance().getMessage("lblChooseNColors", Lang.getNumeral(2));
|
Localizer.getInstance().getMessage("lblChooseNColors", Lang.getNumeral(2));
|
||||||
SpellAbility sa = new SpellAbility.EmptySa(ApiType.ChooseColor, c, takesAction);
|
SpellAbility sa = new SpellAbility.EmptySa(ApiType.ChooseColor, c, takesAction);
|
||||||
sa.putParam("AILogic", "MostProminentInComputerDeck");
|
sa.putParam("AILogic", "MostProminentInComputerDeck");
|
||||||
Set<String> chosenColors = new HashSet<>(takesAction.getController().chooseColors(prompt, sa, 2, 2, colorChoices));
|
ColorSet chosenColors = ColorSet.fromNames(takesAction.getController().chooseColors(prompt, sa, 2, 2, MagicColor.Constant.ONLY_COLORS));
|
||||||
c.setChosenColorID(chosenColors);
|
c.setMarkedColors(chosenColors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2774,21 +2784,35 @@ public class GameAction {
|
|||||||
* the source
|
* the source
|
||||||
* @return true, if successful
|
* @return true, if successful
|
||||||
*/
|
*/
|
||||||
public static boolean attachAuraOnIndirectEnterBattlefield(final Card source, Map<AbilityKey, Object> params) {
|
private boolean attachAuraOnIndirectETB(final Card source, Map<AbilityKey, Object> params) {
|
||||||
// When an Aura ETB without being cast you can choose a valid card to
|
// When an Aura ETB without being cast you can choose a valid card to attach it to
|
||||||
// attach it to
|
if (!source.hasKeyword(Keyword.ENCHANT)) {
|
||||||
final SpellAbility aura = source.getFirstAttachSpell();
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpellAbility aura = source.getCurrentState().getAuraSpell();
|
||||||
if (aura == null) {
|
if (aura == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
aura.setActivatingPlayer(source.getController());
|
|
||||||
final Game game = source.getGame();
|
|
||||||
final TargetRestrictions tgt = aura.getTargetRestrictions();
|
|
||||||
|
|
||||||
|
Set<ZoneType> zones = EnumSet.noneOf(ZoneType.class);
|
||||||
|
boolean canTargetPlayer = false;
|
||||||
|
for (KeywordInterface ki : source.getKeywords(Keyword.ENCHANT)) {
|
||||||
|
String o = ki.getOriginal();
|
||||||
|
String m[] = o.split(":");
|
||||||
|
String v = m[1];
|
||||||
|
if (v.contains("inZone")) { // currently the only other zone is Graveyard
|
||||||
|
zones.add(ZoneType.Graveyard);
|
||||||
|
} else {
|
||||||
|
zones.add(ZoneType.Battlefield);
|
||||||
|
}
|
||||||
|
if (v.startsWith("Player") || v.startsWith("Opponent")) {
|
||||||
|
canTargetPlayer = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Player p = source.getController();
|
Player p = source.getController();
|
||||||
if (tgt.canTgtPlayer()) {
|
if (canTargetPlayer) {
|
||||||
final FCollection<Player> players = game.getPlayers().filter(PlayerPredicates.canBeAttached(source, aura));
|
final FCollection<Player> players = game.getPlayers().filter(PlayerPredicates.canBeAttached(source, null));
|
||||||
|
|
||||||
final Player pa = p.getController().chooseSingleEntityForEffect(players, aura,
|
final Player pa = p.getController().chooseSingleEntityForEffect(players, aura,
|
||||||
Localizer.getInstance().getMessage("lblSelectAPlayerAttachSourceTo", CardTranslation.getTranslatedName(source.getName())), null);
|
Localizer.getInstance().getMessage("lblSelectAPlayerAttachSourceTo", CardTranslation.getTranslatedName(source.getName())), null);
|
||||||
@@ -2797,9 +2821,7 @@ public class GameAction {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
List<ZoneType> zones = Lists.newArrayList(tgt.getZone());
|
|
||||||
CardCollection list = new CardCollection();
|
CardCollection list = new CardCollection();
|
||||||
|
|
||||||
if (params != null) {
|
if (params != null) {
|
||||||
if (zones.contains(ZoneType.Battlefield)) {
|
if (zones.contains(ZoneType.Battlefield)) {
|
||||||
list.addAll((CardCollectionView) params.get(AbilityKey.LastStateBattlefield));
|
list.addAll((CardCollectionView) params.get(AbilityKey.LastStateBattlefield));
|
||||||
@@ -2812,7 +2834,7 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
list.addAll(game.getCardsIn(zones));
|
list.addAll(game.getCardsIn(zones));
|
||||||
|
|
||||||
list = CardLists.filter(list, CardPredicates.canBeAttached(source, aura));
|
list = CardLists.filter(list, CardPredicates.canBeAttached(source, null));
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import forge.game.spellability.*;
|
|||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.staticability.StaticAbilityAlternativeCost;
|
import forge.game.staticability.StaticAbilityAlternativeCost;
|
||||||
import forge.game.staticability.StaticAbilityLayer;
|
import forge.game.staticability.StaticAbilityLayer;
|
||||||
|
import forge.game.staticability.StaticAbilityMode;
|
||||||
import forge.game.zone.Zone;
|
import forge.game.zone.Zone;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
@@ -183,6 +184,34 @@ public final class GameActionUtil {
|
|||||||
flashback.setKeyword(inst);
|
flashback.setKeyword(inst);
|
||||||
flashback.setIntrinsic(inst.isIntrinsic());
|
flashback.setIntrinsic(inst.isIntrinsic());
|
||||||
alternatives.add(flashback);
|
alternatives.add(flashback);
|
||||||
|
} else if (keyword.startsWith("Harmonize")) {
|
||||||
|
if (!source.isInZone(ZoneType.Graveyard)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyword.equals("Harmonize") && source.getManaCost().isNoCost()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpellAbility harmonize = null;
|
||||||
|
|
||||||
|
if (keyword.contains(":")) {
|
||||||
|
final String[] k = keyword.split(":");
|
||||||
|
harmonize = sa.copyWithManaCostReplaced(activator, new Cost(k[1], false));
|
||||||
|
String extraParams = k.length > 2 ? k[2] : "";
|
||||||
|
if (!extraParams.isEmpty()) {
|
||||||
|
for (Map.Entry<String, String> param : AbilityFactory.getMapParams(extraParams).entrySet()) {
|
||||||
|
harmonize.putParam(param.getKey(), param.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
harmonize = sa.copy(activator);
|
||||||
|
}
|
||||||
|
harmonize.setAlternativeCost(AlternativeCost.Harmonize);
|
||||||
|
harmonize.getRestrictions().setZone(ZoneType.Graveyard);
|
||||||
|
harmonize.setKeyword(inst);
|
||||||
|
harmonize.setIntrinsic(inst.isIntrinsic());
|
||||||
|
alternatives.add(harmonize);
|
||||||
} else if (keyword.startsWith("Foretell")) {
|
} else if (keyword.startsWith("Foretell")) {
|
||||||
// Foretell cast only from Exile
|
// Foretell cast only from Exile
|
||||||
if (!source.isInZone(ZoneType.Exile) || !source.isForetold() || source.enteredThisTurn() ||
|
if (!source.isInZone(ZoneType.Exile) || !source.isForetold() || source.enteredThisTurn() ||
|
||||||
@@ -390,7 +419,7 @@ public final class GameActionUtil {
|
|||||||
costSources.addAll(game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES));
|
costSources.addAll(game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES));
|
||||||
for (final Card ca : costSources) {
|
for (final Card ca : costSources) {
|
||||||
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
for (final StaticAbility stAb : ca.getStaticAbilities()) {
|
||||||
if (!stAb.checkConditions("OptionalCost")) {
|
if (!stAb.checkConditions(StaticAbilityMode.OptionalCost)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,9 +611,8 @@ public final class GameActionUtil {
|
|||||||
" or greater>";
|
" or greater>";
|
||||||
final Cost cost = new Cost(casualtyCost, false);
|
final Cost cost = new Cost(casualtyCost, false);
|
||||||
String str = "Pay for Casualty? " + cost.toSimpleString();
|
String str = "Pay for Casualty? " + cost.toSimpleString();
|
||||||
boolean v = pc.addKeywordCost(sa, cost, ki, str);
|
|
||||||
|
|
||||||
if (v) {
|
if (pc.addKeywordCost(sa, cost, ki, str)) {
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = sa.copy();
|
result = sa.copy();
|
||||||
}
|
}
|
||||||
@@ -630,9 +658,7 @@ public final class GameActionUtil {
|
|||||||
final Cost cost = new Cost(k[1], false);
|
final Cost cost = new Cost(k[1], false);
|
||||||
String str = "Pay for Offspring? " + cost.toSimpleString();
|
String str = "Pay for Offspring? " + cost.toSimpleString();
|
||||||
|
|
||||||
boolean v = pc.addKeywordCost(sa, cost, ki, str);
|
if (pc.addKeywordCost(sa, cost, ki, str)) {
|
||||||
|
|
||||||
if (v) {
|
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = sa.copy();
|
result = sa.copy();
|
||||||
}
|
}
|
||||||
@@ -679,6 +705,25 @@ public final class GameActionUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sa.isHarmonize()) {
|
||||||
|
CardCollectionView creatures = activator.getCreaturesInPlay();
|
||||||
|
if (!creatures.isEmpty()) {
|
||||||
|
int max = Aggregates.max(creatures, Card::getNetPower);
|
||||||
|
int n = pc.chooseNumber(sa, "Choose power of creature to tap", 0, max);
|
||||||
|
final String harmonizeCost = "tapXType<1/Creature.powerEQ" + n + "/creature for Harmonize>";
|
||||||
|
final Cost cost = new Cost(harmonizeCost, false);
|
||||||
|
|
||||||
|
if (pc.addKeywordCost(sa, cost, sa.getKeyword(), "Tap creature?")) {
|
||||||
|
if (result == null) {
|
||||||
|
result = sa.copy();
|
||||||
|
}
|
||||||
|
result.getPayCosts().add(cost);
|
||||||
|
reset = true;
|
||||||
|
result.setOptionalKeywordAmount(sa.getKeyword(), n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (host.isCreature()) {
|
if (host.isCreature()) {
|
||||||
String kw = "As an additional cost to cast creature spells," +
|
String kw = "As an additional cost to cast creature spells," +
|
||||||
" you may pay any amount of mana. If you do, that creature enters " +
|
" you may pay any amount of mana. If you do, that creature enters " +
|
||||||
@@ -902,14 +947,16 @@ public final class GameActionUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fromZone != null && !fromZone.is(ZoneType.None)) { // and not a copy
|
if (fromZone != null && !fromZone.is(ZoneType.None)) { // and not a copy
|
||||||
|
// add back to where it came from, hopefully old state
|
||||||
|
// skip GameAction
|
||||||
|
oldCard.getZone().remove(oldCard);
|
||||||
|
|
||||||
// might have been an alternative lki host
|
// might have been an alternative lki host
|
||||||
oldCard = ability.getCardState().getCard();
|
oldCard = ability.getCardState().getCard();
|
||||||
|
|
||||||
oldCard.setCastSA(null);
|
oldCard.setCastSA(null);
|
||||||
oldCard.setCastFrom(null);
|
oldCard.setCastFrom(null);
|
||||||
// add back to where it came from, hopefully old state
|
|
||||||
// skip GameAction
|
|
||||||
oldCard.getZone().remove(oldCard);
|
|
||||||
// in some rare cases the old position no longer exists (Panglacial Wurm + Selvala)
|
// in some rare cases the old position no longer exists (Panglacial Wurm + Selvala)
|
||||||
Integer newPosition = zonePosition >= 0 ? Math.min(zonePosition, fromZone.size()) : null;
|
Integer newPosition = zonePosition >= 0 ? Math.min(zonePosition, fromZone.size()) : null;
|
||||||
fromZone.add(oldCard, newPosition, null, true);
|
fromZone.add(oldCard, newPosition, null, true);
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ import forge.game.card.CounterEnumType;
|
|||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.event.GameEventCardAttachment;
|
import forge.game.event.GameEventCardAttachment;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
|
import forge.game.keyword.KeywordInterface;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.replacement.ReplacementEffect;
|
import forge.game.replacement.ReplacementEffect;
|
||||||
import forge.game.replacement.ReplacementType;
|
import forge.game.replacement.ReplacementType;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.game.staticability.StaticAbilityCantAttach;
|
import forge.game.staticability.StaticAbilityCantAttach;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
@@ -267,15 +267,18 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected boolean canBeEnchantedBy(final Card aura) {
|
protected boolean canBeEnchantedBy(final Card aura) {
|
||||||
// TODO need to check for multiple Enchant Keywords
|
if (!aura.hasKeyword(Keyword.ENCHANT)) {
|
||||||
|
return false;
|
||||||
SpellAbility sa = aura.getFirstAttachSpell();
|
|
||||||
TargetRestrictions tgt = null;
|
|
||||||
if (sa != null) {
|
|
||||||
tgt = sa.getTargetRestrictions();
|
|
||||||
}
|
}
|
||||||
|
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
|
||||||
return tgt != null && isValid(tgt.getValidTgts(), aura.getController(), aura, sa);
|
String k = ki.getOriginal();
|
||||||
|
String m[] = k.split(":");
|
||||||
|
String v = m[1];
|
||||||
|
if (!isValid(v.split(","), aura.getController(), aura, null)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasCounters() {
|
public boolean hasCounters() {
|
||||||
|
|||||||
@@ -159,12 +159,17 @@ public class GameEntityCounterTable extends ForwardingTable<Optional<Player>, Ga
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add ETB flag
|
// Add ETB flag
|
||||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||||
runParams.put(AbilityKey.Cause, cause);
|
runParams.put(AbilityKey.Cause, cause);
|
||||||
if (params != null) {
|
if (params != null) {
|
||||||
runParams.putAll(params);
|
runParams.putAll(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean firstTime = false;
|
||||||
|
if (gm.getKey() instanceof Card c) {
|
||||||
|
firstTime = game.getCounterAddedThisTurn(null, c) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Apply counter after replacement effect
|
// Apply counter after replacement effect
|
||||||
for (Map.Entry<Optional<Player>, Map<CounterType, Integer>> e : values.entrySet()) {
|
for (Map.Entry<Optional<Player>, Map<CounterType, Integer>> e : values.entrySet()) {
|
||||||
boolean remember = cause != null && cause.hasParam("RememberPut");
|
boolean remember = cause != null && cause.hasParam("RememberPut");
|
||||||
@@ -182,6 +187,13 @@ public class GameEntityCounterTable extends ForwardingTable<Optional<Player>, Ga
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result.containsColumn(gm.getKey())) {
|
||||||
|
runParams = AbilityKey.newMap();
|
||||||
|
runParams.put(AbilityKey.Object, gm.getKey());
|
||||||
|
runParams.put(AbilityKey.FirstTime, firstTime);
|
||||||
|
game.getTriggerHandler().runTrigger(TriggerType.CounterTypeAddedAll, runParams, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int totalAdded = totalValues();
|
int totalAdded = totalValues();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import com.google.common.collect.Lists;
|
|||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
import forge.card.CardDb;
|
import forge.card.CardDb;
|
||||||
import forge.card.CardEdition;
|
import forge.card.CardEdition;
|
||||||
import forge.card.CardEdition.CardInSet;
|
import forge.card.CardEdition.EditionEntry;
|
||||||
import forge.card.CardRarity;
|
import forge.card.CardRarity;
|
||||||
import forge.deck.CardPool;
|
import forge.deck.CardPool;
|
||||||
import forge.deck.Deck;
|
import forge.deck.Deck;
|
||||||
@@ -156,7 +156,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
|||||||
for (CardRarity cr: this.getAllowedRarities()) {
|
for (CardRarity cr: this.getAllowedRarities()) {
|
||||||
crp.add(StaticData.instance().getCommonCards().wasPrintedAtRarity(cr));
|
crp.add(StaticData.instance().getCommonCards().wasPrintedAtRarity(cr));
|
||||||
}
|
}
|
||||||
p = p.and(IterableUtil.or(crp));
|
p = p.and(IterableUtil.<PaperCard>or(crp));
|
||||||
}
|
}
|
||||||
if (!this.getAdditionalCards().isEmpty()) {
|
if (!this.getAdditionalCards().isEmpty()) {
|
||||||
p = p.or(PaperCardPredicates.names(this.getAdditionalCards()));
|
p = p.or(PaperCardPredicates.names(this.getAdditionalCards()));
|
||||||
@@ -226,9 +226,9 @@ public class GameFormat implements Comparable<GameFormat> {
|
|||||||
for (String setCode : allowedSetCodes_ro) {
|
for (String setCode : allowedSetCodes_ro) {
|
||||||
CardEdition edition = StaticData.instance().getEditions().get(setCode);
|
CardEdition edition = StaticData.instance().getEditions().get(setCode);
|
||||||
if (edition != null) {
|
if (edition != null) {
|
||||||
for (CardInSet card : edition.getAllCardsInSet()) {
|
for (EditionEntry card : edition.getAllCardsInSet()) {
|
||||||
if (!bannedCardNames_ro.contains(card.name)) {
|
if (!bannedCardNames_ro.contains(card.name())) {
|
||||||
PaperCard pc = commonCards.getCard(card.name, setCode, card.collectorNumber);
|
PaperCard pc = commonCards.getCard(card.name(), setCode, card.collectorNumber());
|
||||||
if (pc != null) {
|
if (pc != null) {
|
||||||
cards.add(pc);
|
cards.add(pc);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ public class GameSnapshot {
|
|||||||
newPlayer.setDamageReceivedThisTurn(origPlayer.getDamageReceivedThisTurn());
|
newPlayer.setDamageReceivedThisTurn(origPlayer.getDamageReceivedThisTurn());
|
||||||
newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn());
|
newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn());
|
||||||
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
|
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
|
||||||
newPlayer.setBlessing(origPlayer.hasBlessing());
|
newPlayer.setBlessing(origPlayer.hasBlessing(), null);
|
||||||
newPlayer.setRevolt(origPlayer.hasRevolt());
|
newPlayer.setRevolt(origPlayer.hasRevolt());
|
||||||
newPlayer.setLibrarySearched(origPlayer.getLibrarySearched());
|
newPlayer.setLibrarySearched(origPlayer.getLibrarySearched());
|
||||||
newPlayer.setSpellsCastLastTurn(origPlayer.getSpellsCastLastTurn());
|
newPlayer.setSpellsCastLastTurn(origPlayer.getSpellsCastLastTurn());
|
||||||
@@ -322,7 +322,7 @@ public class GameSnapshot {
|
|||||||
newCard.setLayerTimestamp(fromCard.getLayerTimestamp());
|
newCard.setLayerTimestamp(fromCard.getLayerTimestamp());
|
||||||
newCard.setTapped(fromCard.isTapped());
|
newCard.setTapped(fromCard.isTapped());
|
||||||
newCard.setFaceDown(fromCard.isFaceDown());
|
newCard.setFaceDown(fromCard.isFaceDown());
|
||||||
newCard.setManifested(fromCard.isManifested());
|
newCard.setManifested(fromCard.getManifestedSA());
|
||||||
newCard.setSickness(fromCard.hasSickness());
|
newCard.setSickness(fromCard.hasSickness());
|
||||||
newCard.setState(fromCard.getCurrentStateName(), false);
|
newCard.setState(fromCard.getCurrentStateName(), false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public enum GameType {
|
|||||||
Winston (DeckFormat.Limited, true, true, true, "lblWinston", ""),
|
Winston (DeckFormat.Limited, true, true, true, "lblWinston", ""),
|
||||||
Gauntlet (DeckFormat.Constructed, false, true, true, "lblGauntlet", ""),
|
Gauntlet (DeckFormat.Constructed, false, true, true, "lblGauntlet", ""),
|
||||||
Tournament (DeckFormat.Constructed, false, true, true, "lblTournament", ""),
|
Tournament (DeckFormat.Constructed, false, true, true, "lblTournament", ""),
|
||||||
CommanderGauntlet (DeckFormat.Commander, false, false, false, "lblCommander", "lblCommanderDesc"),
|
CommanderGauntlet (DeckFormat.Commander, false, false, false, "lblCommanderGauntlet", "lblCommanderDesc"),
|
||||||
Quest (DeckFormat.QuestDeck, true, true, false, "lblQuest", ""),
|
Quest (DeckFormat.QuestDeck, true, true, false, "lblQuest", ""),
|
||||||
QuestDraft (DeckFormat.Limited, true, true, true, "lblQuestDraft", ""),
|
QuestDraft (DeckFormat.Limited, true, true, true, "lblQuestDraft", ""),
|
||||||
PlanarConquest (DeckFormat.PlanarConquest, true, false, false, "lblPlanarConquest", ""),
|
PlanarConquest (DeckFormat.PlanarConquest, true, false, false, "lblPlanarConquest", ""),
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ public interface IHasSVars {
|
|||||||
//public Set<String> getSVars();
|
//public Set<String> getSVars();
|
||||||
|
|
||||||
public Map<String, String> getSVars();
|
public Map<String, String> getSVars();
|
||||||
public Map<String, String> getDirectSVars();
|
|
||||||
|
|
||||||
public void removeSVar(final String var);
|
public void removeSVar(final String var);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,26 +180,19 @@ public final class AbilityFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Cost parseAbilityCost(final CardState state, Map<String, String> mapParams, AbilityRecordType type) {
|
public static Cost parseAbilityCost(final CardState state, Map<String, String> mapParams, AbilityRecordType type) {
|
||||||
Cost abCost = null;
|
if (type == AbilityRecordType.SubAbility) {
|
||||||
if (type != AbilityRecordType.SubAbility) {
|
return null;
|
||||||
String cost = mapParams.get("Cost");
|
}
|
||||||
if (cost == null) {
|
String cost = mapParams.get("Cost");
|
||||||
if (type == AbilityRecordType.Spell) {
|
if (cost != null) {
|
||||||
SpellAbility firstAbility = state.getFirstAbility();
|
return new Cost(cost, type == AbilityRecordType.Ability);
|
||||||
if (firstAbility != null && firstAbility.isSpell()) {
|
}
|
||||||
// TODO might remove when Enchant Keyword is refactored
|
if (type == AbilityRecordType.Spell) {
|
||||||
System.err.println(state.getName() + " already has Spell using mana cost");
|
// for a Spell if no Cost is used, use the card states ManaCost
|
||||||
}
|
return new Cost(state.getManaCost(), false);
|
||||||
// for a Spell if no Cost is used, use the card states ManaCost
|
} else {
|
||||||
abCost = new Cost(state.getManaCost(), false);
|
throw new RuntimeException("AbilityFactory : getAbility -- no Cost in " + state.getName());
|
||||||
} else {
|
|
||||||
throw new RuntimeException("AbilityFactory : getAbility -- no Cost in " + state.getName());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
abCost = new Cost(cost, type == AbilityRecordType.Ability);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return abCost;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SpellAbility getAbility(AbilityRecordType type, ApiType api, Map<String, String> mapParams,
|
public static SpellAbility getAbility(AbilityRecordType type, ApiType api, Map<String, String> mapParams,
|
||||||
@@ -216,15 +209,6 @@ public final class AbilityFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (api == ApiType.PermanentCreature || api == ApiType.PermanentNoncreature) {
|
|
||||||
// If API is a permanent type, and creating AF Spell
|
|
||||||
// Clear out the auto created SpellPermanent spell
|
|
||||||
if (type == AbilityRecordType.Spell
|
|
||||||
&& !mapParams.containsKey("SubAbility") && !mapParams.containsKey("NonBasicSpell")) {
|
|
||||||
hostCard.clearFirstSpell();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (abCost == null) {
|
if (abCost == null) {
|
||||||
abCost = parseAbilityCost(state, mapParams, type);
|
abCost = parseAbilityCost(state, mapParams, type);
|
||||||
}
|
}
|
||||||
@@ -510,8 +494,9 @@ public final class AbilityFactory {
|
|||||||
AbilityRecordType leftType = AbilityRecordType.getRecordType(leftMap);
|
AbilityRecordType leftType = AbilityRecordType.getRecordType(leftMap);
|
||||||
ApiType leftApi = leftType.getApiTypeOf(leftMap);
|
ApiType leftApi = leftType.getApiTypeOf(leftMap);
|
||||||
leftMap.put("StackDescription", leftMap.get("SpellDescription"));
|
leftMap.put("StackDescription", leftMap.get("SpellDescription"));
|
||||||
leftMap.put("SpellDescription", "Fuse (you may cast both halves of this card from your hand).");
|
leftMap.put("SpellDescription", "Fuse (You may cast one or both halves of this card from your hand.)");
|
||||||
leftMap.put("ActivationZone", "Hand");
|
leftMap.put("ActivationZone", "Hand");
|
||||||
|
leftMap.put("Secondary", "True");
|
||||||
|
|
||||||
CardState rightState = card.getState(CardStateName.RightSplit);
|
CardState rightState = card.getState(CardStateName.RightSplit);
|
||||||
SpellAbility rightAbility = rightState.getFirstAbility();
|
SpellAbility rightAbility = rightState.getFirstAbility();
|
||||||
@@ -526,8 +511,10 @@ public final class AbilityFactory {
|
|||||||
totalCost.add(parseAbilityCost(rightState, rightMap, rightType));
|
totalCost.add(parseAbilityCost(rightState, rightMap, rightType));
|
||||||
|
|
||||||
final SpellAbility left = getAbility(leftType, leftApi, leftMap, totalCost, leftState, leftState);
|
final SpellAbility left = getAbility(leftType, leftApi, leftMap, totalCost, leftState, leftState);
|
||||||
|
left.setOriginalAbility(leftAbility);
|
||||||
left.setCardState(card.getState(CardStateName.Original));
|
left.setCardState(card.getState(CardStateName.Original));
|
||||||
final AbilitySub right = (AbilitySub) getAbility(AbilityRecordType.SubAbility, rightApi, rightMap, null, rightState, rightState);
|
final AbilitySub right = (AbilitySub) getAbility(AbilityRecordType.SubAbility, rightApi, rightMap, null, rightState, rightState);
|
||||||
|
right.setOriginalAbility(rightAbility);
|
||||||
left.appendSubAbility(right);
|
left.appendSubAbility(right);
|
||||||
return left;
|
return left;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ public enum AbilityKey {
|
|||||||
Causer("Causer"),
|
Causer("Causer"),
|
||||||
Championed("Championed"),
|
Championed("Championed"),
|
||||||
ClassLevel("ClassLevel"),
|
ClassLevel("ClassLevel"),
|
||||||
Cost("Cost"),
|
|
||||||
CostStack("CostStack"),
|
CostStack("CostStack"),
|
||||||
CounterAmount("CounterAmount"),
|
CounterAmount("CounterAmount"),
|
||||||
CounteredSA("CounteredSA"),
|
CounteredSA("CounteredSA"),
|
||||||
@@ -62,6 +61,7 @@ public enum AbilityKey {
|
|||||||
DefendingPlayer("DefendingPlayer"),
|
DefendingPlayer("DefendingPlayer"),
|
||||||
Destination("Destination"),
|
Destination("Destination"),
|
||||||
Devoured("Devoured"),
|
Devoured("Devoured"),
|
||||||
|
DicePTExchanges("DicePTExchanges"),
|
||||||
Discard("Discard"),
|
Discard("Discard"),
|
||||||
DiscardedBefore("DiscardedBefore"),
|
DiscardedBefore("DiscardedBefore"),
|
||||||
DividedShieldAmount("DividedShieldAmount"),
|
DividedShieldAmount("DividedShieldAmount"),
|
||||||
@@ -72,7 +72,6 @@ public enum AbilityKey {
|
|||||||
Explored("Explored"),
|
Explored("Explored"),
|
||||||
Explorer("Explorer"),
|
Explorer("Explorer"),
|
||||||
ExtraTurn("ExtraTurn"),
|
ExtraTurn("ExtraTurn"),
|
||||||
Event("Event"),
|
|
||||||
ETB("ETB"),
|
ETB("ETB"),
|
||||||
Fighter("Fighter"),
|
Fighter("Fighter"),
|
||||||
Fighters("Fighters"),
|
Fighters("Fighters"),
|
||||||
@@ -94,8 +93,8 @@ public enum AbilityKey {
|
|||||||
Mana("Mana"),
|
Mana("Mana"),
|
||||||
MergedCards("MergedCards"),
|
MergedCards("MergedCards"),
|
||||||
Mode("Mode"),
|
Mode("Mode"),
|
||||||
Modifier("Modifier"),
|
|
||||||
MonstrosityAmount("MonstrosityAmount"),
|
MonstrosityAmount("MonstrosityAmount"),
|
||||||
|
NaturalResult("NaturalResult"),
|
||||||
NewCard("NewCard"),
|
NewCard("NewCard"),
|
||||||
NewCounterAmount("NewCounterAmount"),
|
NewCounterAmount("NewCounterAmount"),
|
||||||
NoPreventDamage("NoPreventDamage"),
|
NoPreventDamage("NoPreventDamage"),
|
||||||
|
|||||||
@@ -1366,7 +1366,7 @@ public class AbilityUtils {
|
|||||||
|
|
||||||
// do blessing there before condition checks
|
// do blessing there before condition checks
|
||||||
if (source.hasKeyword(Keyword.ASCEND) && controller.getZone(ZoneType.Battlefield).size() >= 10) {
|
if (source.hasKeyword(Keyword.ASCEND) && controller.getZone(ZoneType.Battlefield).size() >= 10) {
|
||||||
controller.setBlessing(true);
|
controller.setBlessing(true, source.getSetCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source.hasKeyword(Keyword.GIFT) && sa.isGiftPromised()) {
|
if (source.hasKeyword(Keyword.GIFT) && sa.isGiftPromised()) {
|
||||||
@@ -1621,7 +1621,8 @@ public class AbilityUtils {
|
|||||||
|
|
||||||
final String[] sq;
|
final String[] sq;
|
||||||
sq = l[0].split("\\.");
|
sq = l[0].split("\\.");
|
||||||
|
String[] paidparts = l[0].split("\\$", 2);
|
||||||
|
Iterable<Card> someCards = null;
|
||||||
final Game game = c.getGame();
|
final Game game = c.getGame();
|
||||||
|
|
||||||
if (ctb != null) {
|
if (ctb != null) {
|
||||||
@@ -1786,11 +1787,10 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
// Count$NumTimesChoseMode
|
// Count$NumTimesChoseMode
|
||||||
if (sq[0].startsWith("NumTimesChoseMode")) {
|
if (sq[0].startsWith("NumTimesChoseMode")) {
|
||||||
SpellAbility sub = sa.getRootAbility();
|
|
||||||
int amount = 0;
|
int amount = 0;
|
||||||
while (sub != null) {
|
SpellAbility tail = sa.getTailAbility();
|
||||||
if (sub.getDirectSVars().containsKey("CharmOrder")) amount++;
|
if (tail.hasSVar("CharmOrder")) {
|
||||||
sub = sub.getSubAbility();
|
amount = tail.getSVarInt("CharmOrder");
|
||||||
}
|
}
|
||||||
return doXMath(amount, expr, c, ctb);
|
return doXMath(amount, expr, c, ctb);
|
||||||
}
|
}
|
||||||
@@ -1809,27 +1809,25 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sq[0].startsWith("LastStateBattlefield")) {
|
if (sq[0].startsWith("LastStateBattlefield")) {
|
||||||
final String[] k = l[0].split(" ");
|
final String[] k = paidparts[0].split(" ");
|
||||||
CardCollectionView list;
|
|
||||||
// this is only for spells that were cast
|
// this is only for spells that were cast
|
||||||
if (sq[0].contains("WithFallback")) {
|
if (sq[0].contains("WithFallback")) {
|
||||||
if (!sa.getHostCard().wasCast()) {
|
if (!sa.getHostCard().wasCast()) {
|
||||||
return doXMath(0, expr, c, ctb);
|
return doXMath(0, expr, c, ctb);
|
||||||
}
|
}
|
||||||
list = sa.getHostCard().getCastSA().getLastStateBattlefield();
|
someCards = sa.getHostCard().getCastSA().getLastStateBattlefield();
|
||||||
} else {
|
} else {
|
||||||
list = sa.getLastStateBattlefield();
|
someCards = sa.getLastStateBattlefield();
|
||||||
}
|
}
|
||||||
if (list == null || list.isEmpty()) {
|
if (someCards == null || Iterables.isEmpty(someCards)) {
|
||||||
// LastState is Empty
|
// LastState is Empty
|
||||||
if (sq[0].contains("WithFallback")) {
|
if (sq[0].contains("WithFallback")) {
|
||||||
list = game.getCardsIn(ZoneType.Battlefield);
|
someCards = game.getCardsIn(ZoneType.Battlefield);
|
||||||
} else {
|
} else {
|
||||||
return doXMath(0, expr, c, ctb);
|
return doXMath(0, expr, c, ctb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
list = CardLists.getValidCards(list, k[1], player, c, sa);
|
someCards = CardLists.getValidCards(someCards, k[1], player, c, sa);
|
||||||
return doXMath(list.size(), expr, c, ctb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sq[0].startsWith("LastStateGraveyard")) {
|
if (sq[0].startsWith("LastStateGraveyard")) {
|
||||||
@@ -1856,6 +1854,10 @@ public class AbilityUtils {
|
|||||||
return doXMath(list.size(), expr, c, ctb);
|
return doXMath(list.size(), expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sq[0].equals("ActivatedThisGame")) {
|
||||||
|
return doXMath(sa.getActivationsThisGame(), expr, c, ctb);
|
||||||
|
}
|
||||||
|
|
||||||
if (sq[0].equals("ResolvedThisTurn")) {
|
if (sq[0].equals("ResolvedThisTurn")) {
|
||||||
return doXMath(sa.getResolvedThisTurn(), expr, c, ctb);
|
return doXMath(sa.getResolvedThisTurn(), expr, c, ctb);
|
||||||
}
|
}
|
||||||
@@ -1956,9 +1958,6 @@ public class AbilityUtils {
|
|||||||
return doXMath(sum, expr, c, ctb);
|
return doXMath(sum, expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] paidparts = l[0].split("\\$", 2);
|
|
||||||
Iterable<Card> someCards = null;
|
|
||||||
|
|
||||||
// count valid cards in any specified zone/s
|
// count valid cards in any specified zone/s
|
||||||
if (sq[0].startsWith("Valid")) {
|
if (sq[0].startsWith("Valid")) {
|
||||||
String[] lparts = paidparts[0].split(" ", 2);
|
String[] lparts = paidparts[0].split(" ", 2);
|
||||||
@@ -2217,7 +2216,7 @@ public class AbilityUtils {
|
|||||||
// Count$IfCastInOwnMainPhase.<numMain>.<numNotMain> // 7/10
|
// Count$IfCastInOwnMainPhase.<numMain>.<numNotMain> // 7/10
|
||||||
if (sq[0].contains("IfCastInOwnMainPhase")) {
|
if (sq[0].contains("IfCastInOwnMainPhase")) {
|
||||||
final PhaseHandler cPhase = game.getPhaseHandler();
|
final PhaseHandler cPhase = game.getPhaseHandler();
|
||||||
final boolean isMyMain = cPhase.getPhase().isMain() && cPhase.isPlayerTurn(player) && c.getCastFrom() != null;
|
final boolean isMyMain = cPhase.getPhase().isMain() && cPhase.isPlayerTurn(player) && c.wasCast();
|
||||||
return doXMath(Integer.parseInt(sq[isMyMain ? 1 : 2]), expr, c, ctb);
|
return doXMath(Integer.parseInt(sq[isMyMain ? 1 : 2]), expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2226,6 +2225,11 @@ public class AbilityUtils {
|
|||||||
return doXMath(game.getPhaseHandler().getNumUpkeep() - (game.getPhaseHandler().is(PhaseType.UPKEEP) ? 1 : 0), expr, c, ctb);
|
return doXMath(game.getPhaseHandler().getNumUpkeep() - (game.getPhaseHandler().is(PhaseType.UPKEEP) ? 1 : 0), expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Count$FinishedEndOfTurnsThisTurn
|
||||||
|
if (sq[0].startsWith("FinishedEndOfTurnsThisTurn")) {
|
||||||
|
return doXMath(game.getPhaseHandler().getNumEndOfTurn() - (game.getPhaseHandler().is(PhaseType.END_OF_TURN) ? 1 : 0), expr, c, ctb);
|
||||||
|
}
|
||||||
|
|
||||||
// Count$AttachedTo <restriction>
|
// Count$AttachedTo <restriction>
|
||||||
if (sq[0].startsWith("AttachedTo")) {
|
if (sq[0].startsWith("AttachedTo")) {
|
||||||
final String[] k = l[0].split(" ");
|
final String[] k = l[0].split(" ");
|
||||||
@@ -2268,6 +2272,9 @@ public class AbilityUtils {
|
|||||||
if (sq[0].equals("Delirium")) {
|
if (sq[0].equals("Delirium")) {
|
||||||
return doXMath(calculateAmount(c, sq[player.hasDelirium() ? 1 : 2], ctb), expr, c, ctb);
|
return doXMath(calculateAmount(c, sq[player.hasDelirium() ? 1 : 2], ctb), expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
if (sq[0].equals("MaxSpeed")) {
|
||||||
|
return doXMath(calculateAmount(c, sq[player.maxSpeed() ? 1 : 2], ctb), expr, c, ctb);
|
||||||
|
}
|
||||||
if (sq[0].equals("FatefulHour")) {
|
if (sq[0].equals("FatefulHour")) {
|
||||||
return doXMath(calculateAmount(c, sq[player.getLife() <= 5 ? 1 : 2], ctb), expr, c, ctb);
|
return doXMath(calculateAmount(c, sq[player.getLife() <= 5 ? 1 : 2], ctb), expr, c, ctb);
|
||||||
}
|
}
|
||||||
@@ -2317,6 +2324,10 @@ public class AbilityUtils {
|
|||||||
return doXMath(player.getNumDrawnLastTurn(), expr, c, ctb);
|
return doXMath(player.getNumDrawnLastTurn(), expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sq[0].equals("YouFlipThisTurn")) {
|
||||||
|
return doXMath(player.getNumFlipsThisTurn(), expr, c, ctb);
|
||||||
|
}
|
||||||
|
|
||||||
if (sq[0].equals("YouRollThisTurn")) {
|
if (sq[0].equals("YouRollThisTurn")) {
|
||||||
return doXMath(player.getNumRollsThisTurn(), expr, c, ctb);
|
return doXMath(player.getNumRollsThisTurn(), expr, c, ctb);
|
||||||
}
|
}
|
||||||
@@ -2402,6 +2413,10 @@ public class AbilityUtils {
|
|||||||
return doXMath(player.getMaxOpponentAssignedDamage(), expr, c, ctb);
|
return doXMath(player.getMaxOpponentAssignedDamage(), expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sq[0].equals("MaxCombatDamageThisTurn")) {
|
||||||
|
return doXMath(player.getMaxAssignedCombatDamage(), expr, c, ctb);
|
||||||
|
}
|
||||||
|
|
||||||
if (sq[0].contains("TotalDamageThisTurn")) {
|
if (sq[0].contains("TotalDamageThisTurn")) {
|
||||||
String[] props = l[0].split(" ");
|
String[] props = l[0].split(" ");
|
||||||
int sum = 0;
|
int sum = 0;
|
||||||
@@ -2469,7 +2484,6 @@ public class AbilityUtils {
|
|||||||
// But these aren't really things you count so they'll show up in properties most likely
|
// But these aren't really things you count so they'll show up in properties most likely
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Count$TypesSharedWith [defined]
|
//Count$TypesSharedWith [defined]
|
||||||
if (sq[0].startsWith("TypesSharedWith")) {
|
if (sq[0].startsWith("TypesSharedWith")) {
|
||||||
Set<CardType.CoreType> thisTypes = Sets.newHashSet(c.getType().getCoreTypes());
|
Set<CardType.CoreType> thisTypes = Sets.newHashSet(c.getType().getCoreTypes());
|
||||||
@@ -2699,24 +2713,6 @@ public class AbilityUtils {
|
|||||||
return doXMath(calculateAmount(c, sq[res.size() > 0 ? 1 : 2], ctb), expr, c, ctb);
|
return doXMath(calculateAmount(c, sq[res.size() > 0 ? 1 : 2], ctb), expr, c, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sq[0].startsWith("CreatureType")) {
|
|
||||||
String[] sqparts = l[0].split(" ", 2);
|
|
||||||
final String[] rest = sqparts[1].split(",");
|
|
||||||
|
|
||||||
final CardCollectionView cardsInZones = sqparts[0].length() > 12
|
|
||||||
? game.getCardsIn(ZoneType.listValueOf(sqparts[0].substring(12)))
|
|
||||||
: game.getCardsIn(ZoneType.Battlefield);
|
|
||||||
|
|
||||||
CardCollection cards = CardLists.getValidCards(cardsInZones, rest, player, c, ctb);
|
|
||||||
final Set<String> creatTypes = Sets.newHashSet();
|
|
||||||
|
|
||||||
for (Card card : cards) {
|
|
||||||
creatTypes.addAll(card.getType().getCreatureTypes());
|
|
||||||
}
|
|
||||||
// filter out fun types?
|
|
||||||
return doXMath(creatTypes.size(), expr, c, ctb);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count$Chroma.<color name>
|
// Count$Chroma.<color name>
|
||||||
if (sq[0].startsWith("Chroma")) {
|
if (sq[0].startsWith("Chroma")) {
|
||||||
final CardCollectionView cards;
|
final CardCollectionView cards;
|
||||||
@@ -2775,16 +2771,6 @@ public class AbilityUtils {
|
|||||||
return game.getPhaseHandler().getPlanarDiceSpecialActionThisTurn();
|
return game.getPhaseHandler().getPlanarDiceSpecialActionThisTurn();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sq[0].equals("AllTypes")) {
|
|
||||||
List<Card> cards = getDefinedCards(c, sq[1], ctb);
|
|
||||||
|
|
||||||
int amount = countCardTypesFromList(cards, false) +
|
|
||||||
countSuperTypesFromList(cards) +
|
|
||||||
countSubTypesFromList(cards);
|
|
||||||
|
|
||||||
return doXMath(amount, expr, c, ctb);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sq[0].equals("TotalTurns")) {
|
if (sq[0].equals("TotalTurns")) {
|
||||||
return doXMath(game.getPhaseHandler().getTurn(), expr, c, ctb);
|
return doXMath(game.getPhaseHandler().getTurn(), expr, c, ctb);
|
||||||
}
|
}
|
||||||
@@ -2930,18 +2916,6 @@ public class AbilityUtils {
|
|||||||
return doXMath(colorSize[colorSize.length - 2], expr, c, ctb);
|
return doXMath(colorSize[colorSize.length - 2], 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);
|
|
||||||
return doXMath(CardUtil.getColorsFromCards(list).countColors(), expr, c, ctb);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sq[0].startsWith("ColorsDefined")) {
|
|
||||||
final String restriction = l[0].substring(14);
|
|
||||||
final CardCollection list = getDefinedCards(c, restriction, ctb);
|
|
||||||
return doXMath(CardUtil.getColorsFromCards(list).countColors(), expr, c, ctb);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO move below to handlePaid
|
// TODO move below to handlePaid
|
||||||
if (sq[0].startsWith("SumPower")) {
|
if (sq[0].startsWith("SumPower")) {
|
||||||
final String[] restrictions = l[0].split("_");
|
final String[] restrictions = l[0].split("_");
|
||||||
@@ -3426,17 +3400,14 @@ public class AbilityUtils {
|
|||||||
return doXMath(numTied, m, source, ctb);
|
return doXMath(numTied, m, source, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String[] sq;
|
|
||||||
sq = l[0].split("\\.");
|
|
||||||
|
|
||||||
// the number of players passed in
|
// the number of players passed in
|
||||||
if (sq[0].equals("Amount")) {
|
if (l[0].equals("Amount")) {
|
||||||
return doXMath(players.size(), m, source, ctb);
|
return doXMath(players.size(), m, source, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sq[0].startsWith("HasProperty")) {
|
if (l[0].startsWith("HasProperty")) {
|
||||||
int totPlayer = 0;
|
int totPlayer = 0;
|
||||||
String property = sq[0].substring(11);
|
String property = l[0].substring(11);
|
||||||
for (Player p : players) {
|
for (Player p : players) {
|
||||||
if (p.hasProperty(property, controller, source, ctb)) {
|
if (p.hasProperty(property, controller, source, ctb)) {
|
||||||
totPlayer++;
|
totPlayer++;
|
||||||
@@ -3460,7 +3431,7 @@ public class AbilityUtils {
|
|||||||
return doXMath(totPlayer, m, source, ctb);
|
return doXMath(totPlayer, m, source, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sq[0].contains("DamageThisTurn")) {
|
if (l[0].contains("DamageThisTurn")) {
|
||||||
int totDmg = 0;
|
int totDmg = 0;
|
||||||
for (Player p : players) {
|
for (Player p : players) {
|
||||||
totDmg += p.getAssignedDamage();
|
totDmg += p.getAssignedDamage();
|
||||||
@@ -3788,6 +3759,10 @@ public class AbilityUtils {
|
|||||||
return Aggregates.max(paidList, Card::getCMC);
|
return Aggregates.max(paidList, Card::getCMC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.equals("Colors")) {
|
||||||
|
return CardUtil.getColorsFromCards(paidList).countColors();
|
||||||
|
}
|
||||||
|
|
||||||
if (string.equals("DifferentColorPair")) {
|
if (string.equals("DifferentColorPair")) {
|
||||||
final Set<ColorSet> diffPair = new HashSet<>();
|
final Set<ColorSet> diffPair = new HashSet<>();
|
||||||
for (final Card card : paidList) {
|
for (final Card card : paidList) {
|
||||||
@@ -3817,10 +3792,25 @@ public class AbilityUtils {
|
|||||||
return doXMath(num, splitString.length > 1 ? splitString[1] : null, source, ctb);
|
return doXMath(num, splitString.length > 1 ? splitString[1] : null, source, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.startsWith("AllTypes")) {
|
||||||
|
return countCardTypesFromList(paidList, false) +
|
||||||
|
countSuperTypesFromList(paidList) +
|
||||||
|
countSubTypesFromList(paidList);
|
||||||
|
}
|
||||||
|
|
||||||
if (string.startsWith("CardTypes")) {
|
if (string.startsWith("CardTypes")) {
|
||||||
return doXMath(countCardTypesFromList(paidList, string.startsWith("CardTypesPermanent")), CardFactoryUtil.extractOperators(string), source, ctb);
|
return doXMath(countCardTypesFromList(paidList, string.startsWith("CardTypesPermanent")), CardFactoryUtil.extractOperators(string), source, ctb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.startsWith("CreatureType")) {
|
||||||
|
final Set<String> creatTypes = Sets.newHashSet();
|
||||||
|
for (Card card : paidList) {
|
||||||
|
creatTypes.addAll(card.getType().getCreatureTypes());
|
||||||
|
}
|
||||||
|
// filter out fun types?
|
||||||
|
return doXMath(creatTypes.size(), CardFactoryUtil.extractOperators(string), source, ctb);
|
||||||
|
}
|
||||||
|
|
||||||
String filteredString = string;
|
String filteredString = string;
|
||||||
Iterable<Card> filteredList = paidList;
|
Iterable<Card> filteredList = paidList;
|
||||||
final String[] filter = filteredString.split("_");
|
final String[] filter = filteredString.split("_");
|
||||||
|
|||||||
@@ -85,12 +85,14 @@ public enum ApiType {
|
|||||||
Encode (EncodeEffect.class),
|
Encode (EncodeEffect.class),
|
||||||
EndCombatPhase (EndCombatPhaseEffect.class),
|
EndCombatPhase (EndCombatPhaseEffect.class),
|
||||||
EndTurn (EndTurnEffect.class),
|
EndTurn (EndTurnEffect.class),
|
||||||
|
Endure (EndureEffect.class),
|
||||||
ExchangeLife (LifeExchangeEffect.class),
|
ExchangeLife (LifeExchangeEffect.class),
|
||||||
ExchangeLifeVariant (LifeExchangeVariantEffect.class),
|
ExchangeLifeVariant (LifeExchangeVariantEffect.class),
|
||||||
ExchangeControl (ControlExchangeEffect.class),
|
ExchangeControl (ControlExchangeEffect.class),
|
||||||
ExchangeControlVariant (ControlExchangeVariantEffect.class),
|
ExchangeControlVariant (ControlExchangeVariantEffect.class),
|
||||||
ExchangePower (PowerExchangeEffect.class),
|
ExchangePower (PowerExchangeEffect.class),
|
||||||
ExchangeZone (ZoneExchangeEffect.class),
|
ExchangeZone (ZoneExchangeEffect.class),
|
||||||
|
ExchangeTextBox (TextBoxExchangeEffect.class),
|
||||||
Explore (ExploreEffect.class),
|
Explore (ExploreEffect.class),
|
||||||
Fight (FightEffect.class),
|
Fight (FightEffect.class),
|
||||||
FlipACoin (FlipCoinEffect.class),
|
FlipACoin (FlipCoinEffect.class),
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ public abstract class SpellAbilityEffect {
|
|||||||
if ("SpellDescription".equalsIgnoreCase(stackDesc)) {
|
if ("SpellDescription".equalsIgnoreCase(stackDesc)) {
|
||||||
if (params.containsKey("SpellDescription")) {
|
if (params.containsKey("SpellDescription")) {
|
||||||
String rawSDesc = params.get("SpellDescription");
|
String rawSDesc = params.get("SpellDescription");
|
||||||
if (rawSDesc.contains(",,,,,,")) rawSDesc = rawSDesc.replaceAll(",,,,,,", " ");
|
if (rawSDesc.contains(",,,,,,")) rawSDesc = rawSDesc.replace(",,,,,,", " ");
|
||||||
if (rawSDesc.contains(",,,")) rawSDesc = rawSDesc.replaceAll(",,,", " ");
|
if (rawSDesc.contains(",,,")) rawSDesc = rawSDesc.replace(",,,", " ");
|
||||||
String spellDesc = CardTranslation.translateSingleDescriptionText(rawSDesc, sa.getHostCard());
|
String spellDesc = CardTranslation.translateSingleDescriptionText(rawSDesc, sa.getHostCard());
|
||||||
|
|
||||||
//trim reminder text from StackDesc
|
//trim reminder text from StackDesc
|
||||||
@@ -356,6 +356,7 @@ public abstract class SpellAbilityEffect {
|
|||||||
boolean intrinsic = sa.isIntrinsic();
|
boolean intrinsic = sa.isIntrinsic();
|
||||||
boolean your = location.startsWith("Your");
|
boolean your = location.startsWith("Your");
|
||||||
boolean combat = location.endsWith("Combat");
|
boolean combat = location.endsWith("Combat");
|
||||||
|
boolean upkeep = location.endsWith("Upkeep");
|
||||||
|
|
||||||
String desc = sa.getParamOrDefault("AtEOTDesc", "");
|
String desc = sa.getParamOrDefault("AtEOTDesc", "");
|
||||||
|
|
||||||
@@ -365,11 +366,16 @@ public abstract class SpellAbilityEffect {
|
|||||||
if (combat) {
|
if (combat) {
|
||||||
location = location.substring(0, location.length() - "Combat".length());
|
location = location.substring(0, location.length() - "Combat".length());
|
||||||
}
|
}
|
||||||
|
if (upkeep) {
|
||||||
|
location = location.substring(0, location.length() - "Upkeep".length());
|
||||||
|
}
|
||||||
|
|
||||||
if (desc.isEmpty()) {
|
if (desc.isEmpty()) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
if (location.equals("Hand")) {
|
if (location.equals("Hand")) {
|
||||||
sb.append("Return ");
|
sb.append("Return ");
|
||||||
|
} else if (location.equals("Library")) {
|
||||||
|
sb.append("Shuffle ");
|
||||||
} else if (location.equals("SacrificeCtrl")) {
|
} else if (location.equals("SacrificeCtrl")) {
|
||||||
sb.append("Its controller sacrifices ");
|
sb.append("Its controller sacrifices ");
|
||||||
} else {
|
} else {
|
||||||
@@ -378,6 +384,8 @@ public abstract class SpellAbilityEffect {
|
|||||||
sb.append(Lang.joinHomogenous(crds));
|
sb.append(Lang.joinHomogenous(crds));
|
||||||
if (location.equals("Hand")) {
|
if (location.equals("Hand")) {
|
||||||
sb.append(" to your hand");
|
sb.append(" to your hand");
|
||||||
|
} else if (location.equals("Library")) {
|
||||||
|
sb.append(" into your library");
|
||||||
}
|
}
|
||||||
sb.append(" at the ");
|
sb.append(" at the ");
|
||||||
if (combat) {
|
if (combat) {
|
||||||
@@ -385,14 +393,18 @@ public abstract class SpellAbilityEffect {
|
|||||||
} else {
|
} else {
|
||||||
sb.append("beginning of ");
|
sb.append("beginning of ");
|
||||||
sb.append(your ? "your" : "the");
|
sb.append(your ? "your" : "the");
|
||||||
sb.append(" next end step.");
|
if (upkeep) {
|
||||||
|
sb.append(" next upkeep.");
|
||||||
|
} else {
|
||||||
|
sb.append(" next end step.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
desc = sb.toString();
|
desc = sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder delTrig = new StringBuilder();
|
StringBuilder delTrig = new StringBuilder();
|
||||||
delTrig.append("Mode$ Phase | Phase$ ");
|
delTrig.append("Mode$ Phase | Phase$ ");
|
||||||
delTrig.append(combat ? "EndCombat " : "End Of Turn ");
|
delTrig.append(combat ? "EndCombat " : upkeep ? "Upkeep" : "End Of Turn ");
|
||||||
|
|
||||||
if (your) {
|
if (your) {
|
||||||
delTrig.append("| ValidPlayer$ You ");
|
delTrig.append("| ValidPlayer$ You ");
|
||||||
@@ -410,6 +422,8 @@ public abstract class SpellAbilityEffect {
|
|||||||
String trigSA = "";
|
String trigSA = "";
|
||||||
if (location.equals("Hand")) {
|
if (location.equals("Hand")) {
|
||||||
trigSA = "DB$ ChangeZone | Defined$ DelayTriggerRememberedLKI | Origin$ Battlefield | Destination$ Hand";
|
trigSA = "DB$ ChangeZone | Defined$ DelayTriggerRememberedLKI | Origin$ Battlefield | Destination$ Hand";
|
||||||
|
} else if (location.equals("Library")) {
|
||||||
|
trigSA = "DB$ ChangeZone | Defined$ DelayTriggerRememberedLKI | Origin$ Battlefield | Destination$ Library | Shuffle$ True";
|
||||||
} else if (location.equals("SacrificeCtrl")) {
|
} else if (location.equals("SacrificeCtrl")) {
|
||||||
trigSA = "DB$ SacrificeAll | Defined$ DelayTriggerRememberedLKI";
|
trigSA = "DB$ SacrificeAll | Defined$ DelayTriggerRememberedLKI";
|
||||||
} else if (location.equals("Sacrifice")) {
|
} else if (location.equals("Sacrifice")) {
|
||||||
@@ -767,7 +781,11 @@ public abstract class SpellAbilityEffect {
|
|||||||
return combatChanged;
|
return combatChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static GameCommand untilHostLeavesPlayCommand(final CardZoneTable triggerList, final SpellAbility sa) {
|
protected static void changeZoneUntilCommand(final CardZoneTable triggerList, final SpellAbility sa) {
|
||||||
|
if (!sa.hasParam("Duration")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final Card hostCard = sa.getHostCard();
|
final Card hostCard = sa.getHostCard();
|
||||||
final Game game = hostCard.getGame();
|
final Game game = hostCard.getGame();
|
||||||
hostCard.addUntilLeavesBattlefield(triggerList.allCards());
|
hostCard.addUntilLeavesBattlefield(triggerList.allCards());
|
||||||
@@ -782,7 +800,7 @@ public abstract class SpellAbilityEffect {
|
|||||||
lki = null;
|
lki = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GameCommand() {
|
GameCommand gc = new GameCommand() {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@@ -837,6 +855,13 @@ public abstract class SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// corner case can lead to host exiling itself during the effect
|
||||||
|
if (sa.getParam("Duration").contains("UntilHostLeavesPlay") && !hostCard.isInPlay()) {
|
||||||
|
gc.run();
|
||||||
|
} else {
|
||||||
|
addUntilCommand(sa, gc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void discard(SpellAbility sa, final boolean effect, Map<Player, CardCollectionView> discardedMap, Map<AbilityKey, Object> params) {
|
protected static void discard(SpellAbility sa, final boolean effect, Map<Player, CardCollectionView> discardedMap, Map<AbilityKey, Object> params) {
|
||||||
@@ -897,6 +922,8 @@ public abstract class SpellAbilityEffect {
|
|||||||
} else {
|
} else {
|
||||||
game.getUpkeep().addUntilEnd(controller, until);
|
game.getUpkeep().addUntilEnd(controller, until);
|
||||||
}
|
}
|
||||||
|
} else if ("UntilTheEndOfYourNextUntap".equals(duration)) {
|
||||||
|
game.getUntap().addUntilEnd(controller, until);
|
||||||
} else if ("UntilNextEndStep".equals(duration)) {
|
} else if ("UntilNextEndStep".equals(duration)) {
|
||||||
game.getEndOfTurn().addAt(until);
|
game.getEndOfTurn().addAt(until);
|
||||||
} else if ("UntilYourNextEndStep".equals(duration)) {
|
} else if ("UntilYourNextEndStep".equals(duration)) {
|
||||||
@@ -997,8 +1024,9 @@ public abstract class SpellAbilityEffect {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Player getNewChooser(final SpellAbility sa, final Player activator, final Player loser) {
|
public static Player getNewChooser(final SpellAbility sa, final Player loser) {
|
||||||
// CR 800.4g
|
// CR 800.4g
|
||||||
|
final Player activator = sa.getActivatingPlayer();
|
||||||
final PlayerCollection options;
|
final PlayerCollection options;
|
||||||
if (loser.isOpponentOf(activator)) {
|
if (loser.isOpponentOf(activator)) {
|
||||||
options = activator.getOpponents();
|
options = activator.getOpponents();
|
||||||
|
|||||||
@@ -165,14 +165,8 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
|||||||
|
|
||||||
// remove abilities
|
// remove abilities
|
||||||
final List<SpellAbility> removedAbilities = Lists.newArrayList();
|
final List<SpellAbility> removedAbilities = Lists.newArrayList();
|
||||||
boolean clearSpells = sa.hasParam("OverwriteSpells");
|
if (sa.hasParam("RemoveThisAbility")) {
|
||||||
|
removedAbilities.add(sa.getOriginalAbility());
|
||||||
if (clearSpells) {
|
|
||||||
removedAbilities.addAll(Lists.newArrayList(c.getSpells()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.hasParam("RemoveThisAbility") && !removedAbilities.contains(sa)) {
|
|
||||||
removedAbilities.add(sa);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// give abilities
|
// give abilities
|
||||||
@@ -252,9 +246,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!"Permanent".equals(duration) && !perpetual) {
|
if (!"Permanent".equals(duration) && !perpetual) {
|
||||||
if ("UntilControllerNextUntap".equals(duration)) {
|
if ("UntilAnimatedFaceup".equals(duration)) {
|
||||||
game.getUntap().addUntil(c.getController(), unanimate);
|
|
||||||
} else if ("UntilAnimatedFaceup".equals(duration)) {
|
|
||||||
c.addFaceupCommand(unanimate);
|
c.addFaceupCommand(unanimate);
|
||||||
} else {
|
} else {
|
||||||
addUntilCommand(sa, unanimate);
|
addUntilCommand(sa, unanimate);
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public class AscendEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
// Player need 10+ permanents on the battlefield
|
// Player need 10+ permanents on the battlefield
|
||||||
if (p.getZone(ZoneType.Battlefield).size() >= 10) {
|
if (p.getZone(ZoneType.Battlefield).size() >= 10) {
|
||||||
p.setBlessing(true);
|
p.setBlessing(true, sa.getOriginalHost().getSetCode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public class BecomeMonarchEffect extends SpellAbilityEffect {
|
|||||||
@Override
|
@Override
|
||||||
public void resolve(SpellAbility sa) {
|
public void resolve(SpellAbility sa) {
|
||||||
// TODO: improve ai and fix corner cases
|
// TODO: improve ai and fix corner cases
|
||||||
final String set = sa.getHostCard().getSetCode();
|
final String set = sa.getOriginalHost().getSetCode();
|
||||||
|
|
||||||
for (final Player p : getTargetPlayers(sa)) {
|
for (final Player p : getTargetPlayers(sa)) {
|
||||||
if (!p.isInGame()) {
|
if (!p.isInGame()) {
|
||||||
|
|||||||
@@ -186,10 +186,12 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
triggerList.triggerChangesZoneAll(game, sa);
|
triggerList.triggerChangesZoneAll(game, sa);
|
||||||
|
|
||||||
if (sa.hasParam("Duration")) {
|
if (sa.hasParam("AtEOT") && !triggerList.isEmpty()) {
|
||||||
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, sa));
|
registerDelayedTrigger(sa, sa.getParam("AtEOT"), triggerList.allCards());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeZoneUntilCommand(triggerList, sa);
|
||||||
|
|
||||||
// CR 701.20d If an effect would cause a player to shuffle a set of objects into a library,
|
// CR 701.20d If an effect would cause a player to shuffle a set of objects into a library,
|
||||||
// that library is shuffled even if there are no objects in that set.
|
// that library is shuffled even if there are no objects in that set.
|
||||||
if (sa.hasParam("Shuffle")) {
|
if (sa.hasParam("Shuffle")) {
|
||||||
|
|||||||
@@ -663,9 +663,24 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
if (sa.isKeyword(Keyword.UNEARTH) && movedCard.isInPlay()) {
|
if (sa.isKeyword(Keyword.UNEARTH) && movedCard.isInPlay()) {
|
||||||
movedCard.setUnearthed(true);
|
movedCard.setUnearthed(true);
|
||||||
movedCard.addChangedCardKeywords(Lists.newArrayList("Haste"), null, false, game.getNextTimestamp(), null);
|
|
||||||
|
final Card eff = createEffect(sa, sa.getActivatingPlayer(), "Unearth Effect", hostCard.getImageKey());
|
||||||
|
|
||||||
|
// It gains haste.
|
||||||
|
String s = "Mode$ Continuous | Affected$ Card.IsRemembered | EffectZone$ Command | AddKeyword$ Haste";
|
||||||
|
eff.addStaticAbility(s);
|
||||||
|
|
||||||
|
// If it would leave the battlefield, exile it instead of putting it anywhere else.
|
||||||
|
addLeaveBattlefieldReplacement(eff, "Exile");
|
||||||
|
|
||||||
|
eff.addRemembered(movedCard);
|
||||||
|
|
||||||
|
movedCard.addLeavesPlayCommand(exileEffectCommand(game, eff));
|
||||||
|
|
||||||
|
game.getAction().moveToCommand(eff, sa);
|
||||||
|
|
||||||
|
// Exile it at the beginning of the next end step.
|
||||||
registerDelayedTrigger(sa, "Exile", Lists.newArrayList(movedCard));
|
registerDelayedTrigger(sa, "Exile", Lists.newArrayList(movedCard));
|
||||||
addLeaveBattlefieldReplacement(movedCard, sa, "Exile");
|
|
||||||
}
|
}
|
||||||
if (sa.hasParam("LeaveBattlefield")) {
|
if (sa.hasParam("LeaveBattlefield")) {
|
||||||
addLeaveBattlefieldReplacement(movedCard, sa, sa.getParam("LeaveBattlefield"));
|
addLeaveBattlefieldReplacement(movedCard, sa, sa.getParam("LeaveBattlefield"));
|
||||||
@@ -732,8 +747,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
if (sa.hasParam("ForetoldCost")) {
|
if (sa.hasParam("ForetoldCost")) {
|
||||||
movedCard.setForetoldCostByEffect(true);
|
movedCard.setForetoldCostByEffect(true);
|
||||||
}
|
}
|
||||||
// look at the exiled card
|
}
|
||||||
movedCard.addMayLookTemp(activator);
|
// look at the exiled card
|
||||||
|
if (sa.hasParam("WithMayLook") || sa.hasParam("Foretold")) {
|
||||||
|
movedCard.addMayLookFaceDownExile(activator);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CR 400.7k
|
// CR 400.7k
|
||||||
@@ -817,9 +834,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
if (sa.hasParam("AtEOT") && !triggerList.isEmpty()) {
|
if (sa.hasParam("AtEOT") && !triggerList.isEmpty()) {
|
||||||
registerDelayedTrigger(sa, sa.getParam("AtEOT"), triggerList.allCards());
|
registerDelayedTrigger(sa, sa.getParam("AtEOT"), triggerList.allCards());
|
||||||
}
|
}
|
||||||
if ("UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
|
|
||||||
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, sa));
|
changeZoneUntilCommand(triggerList, sa);
|
||||||
}
|
|
||||||
|
|
||||||
// might set after card is moved again if something has changed
|
// might set after card is moved again if something has changed
|
||||||
if (destination.equals(ZoneType.Exile)) {
|
if (destination.equals(ZoneType.Exile)) {
|
||||||
@@ -1024,6 +1040,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
handleCastWhileSearching(fetchList, decider);
|
handleCastWhileSearching(fetchList, decider);
|
||||||
}
|
}
|
||||||
|
if (sa.hasParam("RememberSearched")) {
|
||||||
|
source.addRemembered(player);
|
||||||
|
}
|
||||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(decider);
|
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(decider);
|
||||||
runParams.put(AbilityKey.Target, player);
|
runParams.put(AbilityKey.Target, player);
|
||||||
game.getTriggerHandler().runTrigger(TriggerType.SearchedLibrary, runParams, false);
|
game.getTriggerHandler().runTrigger(TriggerType.SearchedLibrary, runParams, false);
|
||||||
@@ -1129,7 +1148,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// If we're choosing multiple cards, only need to show the reveal dialog the first time through.
|
// If we're choosing multiple cards, only need to show the reveal dialog the first time through.
|
||||||
boolean shouldReveal = (i == 0);
|
boolean shouldReveal = (i == 0);
|
||||||
Card c = null;
|
Card c = null;
|
||||||
@@ -1364,8 +1382,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
if (sa.hasParam("ForetoldCost")) {
|
if (sa.hasParam("ForetoldCost")) {
|
||||||
movedCard.setForetoldCostByEffect(true);
|
movedCard.setForetoldCostByEffect(true);
|
||||||
}
|
}
|
||||||
// look at the exiled card
|
}
|
||||||
movedCard.addMayLookTemp(sa.getActivatingPlayer());
|
|
||||||
|
// look at the exiled card
|
||||||
|
if (sa.hasParam("WithMayLook") || sa.hasParam("Foretold")) {
|
||||||
|
movedCard.addMayLookFaceDownExile(sa.getActivatingPlayer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1461,9 +1482,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
triggerList.triggerChangesZoneAll(game, sa);
|
triggerList.triggerChangesZoneAll(game, sa);
|
||||||
|
|
||||||
if ("UntilHostLeavesPlay".equals(sa.getParam("Duration"))) {
|
changeZoneUntilCommand(triggerList, sa);
|
||||||
addUntilCommand(sa, untilHostLeavesPlayCommand(triggerList, sa));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCastWhileSearching(final CardCollection fetchList, final Player decider) {
|
private void handleCastWhileSearching(final CardCollection fetchList, final Player decider) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import forge.game.ability.AbilityUtils;
|
|||||||
import forge.game.ability.SpellAbilityEffect;
|
import forge.game.ability.SpellAbilityEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -45,7 +46,7 @@ public class CharmEffect extends SpellAbilityEffect {
|
|||||||
choices.removeAll(toRemove);
|
choices.removeAll(toRemove);
|
||||||
}
|
}
|
||||||
|
|
||||||
int indx = 0;
|
int indx = 1;
|
||||||
// set CharmOrder
|
// set CharmOrder
|
||||||
for (AbilitySub sub : choices) {
|
for (AbilitySub sub : choices) {
|
||||||
sub.setSVar("CharmOrder", Integer.toString(indx));
|
sub.setSVar("CharmOrder", Integer.toString(indx));
|
||||||
@@ -89,12 +90,13 @@ public class CharmEffect extends SpellAbilityEffect {
|
|||||||
boolean limit = sa.hasParam("ActivationLimit");
|
boolean limit = sa.hasParam("ActivationLimit");
|
||||||
boolean gameLimit = sa.hasParam("GameActivationLimit");
|
boolean gameLimit = sa.hasParam("GameActivationLimit");
|
||||||
boolean oppChooses = "Opponent".equals(sa.getParam("Chooser"));
|
boolean oppChooses = "Opponent".equals(sa.getParam("Chooser"));
|
||||||
boolean spree = sa.hasParam("Spree");
|
boolean spree = source.hasKeyword(Keyword.SPREE);
|
||||||
|
boolean tiered = source.hasKeyword(Keyword.TIERED);
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append(sa.getCostDescription());
|
sb.append(sa.getCostDescription());
|
||||||
|
|
||||||
if (!spree) {
|
if (!spree && !tiered) {
|
||||||
sb.append(oppChooses ? "An opponent chooses " : "Choose ");
|
sb.append(oppChooses ? "An opponent chooses " : "Choose ");
|
||||||
if (isX) {
|
if (isX) {
|
||||||
sb.append(sa.hasParam("MinCharmNum") && min == 0 ? "up to " : "").append("X");
|
sb.append(sa.hasParam("MinCharmNum") && min == 0 ? "up to " : "").append("X");
|
||||||
@@ -163,7 +165,7 @@ public class CharmEffect extends SpellAbilityEffect {
|
|||||||
if (!includeChosen) {
|
if (!includeChosen) {
|
||||||
sb.append(num == 1 ? " mode." : " modes.");
|
sb.append(num == 1 ? " mode." : " modes.");
|
||||||
} else if (!list.isEmpty()) {
|
} else if (!list.isEmpty()) {
|
||||||
if (!spree) {
|
if (!spree && !tiered) {
|
||||||
if (!repeat && !additionalDesc && !limit && !gameLimit) {
|
if (!repeat && !additionalDesc && !limit && !gameLimit) {
|
||||||
sb.append(" \u2014");
|
sb.append(" \u2014");
|
||||||
}
|
}
|
||||||
@@ -171,7 +173,10 @@ public class CharmEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
for (AbilitySub sub : list) {
|
for (AbilitySub sub : list) {
|
||||||
if (spree) {
|
if (spree) {
|
||||||
sb.append("+ " + new Cost(sub.getParam("SpreeCost"), false).toSimpleString() + " \u2014 ");
|
sb.append("+ " + new Cost(sub.getParam("ModeCost"), false).toSimpleString() + " \u2014 ");
|
||||||
|
} else if (tiered) {
|
||||||
|
sb.append("\u2022 ").append(sub.getParam("PrecostDesc")).append(" \u2014 ");
|
||||||
|
sb.append(new Cost(sub.getParam("ModeCost"), false).toSimpleString() + " \u2014 ");
|
||||||
} else if (sub.hasParam("Pawprint")) {
|
} else if (sub.hasParam("Pawprint")) {
|
||||||
sb.append(StringUtils.repeat("{P}", Integer.parseInt(sub.getParam("Pawprint"))) + " \u2014 ");
|
sb.append(StringUtils.repeat("{P}", Integer.parseInt(sub.getParam("Pawprint"))) + " \u2014 ");
|
||||||
} else {
|
} else {
|
||||||
@@ -271,10 +276,14 @@ public class CharmEffect extends SpellAbilityEffect {
|
|||||||
// Sort Chosen by SA order
|
// Sort Chosen by SA order
|
||||||
chosen.sort(Comparator.comparingInt(o -> o.getSVarInt("CharmOrder")));
|
chosen.sort(Comparator.comparingInt(o -> o.getSVarInt("CharmOrder")));
|
||||||
|
|
||||||
|
int indx = 1;
|
||||||
for (AbilitySub sub : chosen) {
|
for (AbilitySub sub : chosen) {
|
||||||
// Clone the chosen, just in case the same subAb gets chosen multiple times
|
// Clone the chosen, just in case the same subAb gets chosen multiple times
|
||||||
AbilitySub clone = (AbilitySub)sub.copy(sa.getActivatingPlayer());
|
AbilitySub clone = (AbilitySub)sub.copy(sa.getActivatingPlayer());
|
||||||
|
|
||||||
|
clone.setSVar("CharmOrder", Integer.toString(indx));
|
||||||
|
indx++;
|
||||||
|
|
||||||
// make StackDescription be the SpellDescription if it doesn't already have one
|
// make StackDescription be the SpellDescription if it doesn't already have one
|
||||||
if (!clone.hasParam("StackDescription")) {
|
if (!clone.hasParam("StackDescription")) {
|
||||||
clone.putParam("StackDescription", "SpellDescription");
|
clone.putParam("StackDescription", "SpellDescription");
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ public class ChooseCardEffect extends SpellAbilityEffect {
|
|||||||
CardCollectionView pChoices = choices;
|
CardCollectionView pChoices = choices;
|
||||||
CardCollection chosen = new CardCollection();
|
CardCollection chosen = new CardCollection();
|
||||||
if (!p.isInGame()) {
|
if (!p.isInGame()) {
|
||||||
p = getNewChooser(sa, activator, p);
|
p = getNewChooser(sa, p);
|
||||||
}
|
}
|
||||||
if (sa.hasParam("ControlledByPlayer")) {
|
if (sa.hasParam("ControlledByPlayer")) {
|
||||||
final String param = sa.getParam("ControlledByPlayer");
|
final String param = sa.getParam("ControlledByPlayer");
|
||||||
@@ -131,7 +131,7 @@ public class ChooseCardEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
} else if (sa.hasParam("ChooseEach")) {
|
} else if (sa.hasParam("ChooseEach")) {
|
||||||
final String s = sa.getParam("ChooseEach");
|
final String s = sa.getParam("ChooseEach");
|
||||||
final String[] types = s.equals("Party") ? new String[]{"Cleric","Thief","Warrior","Wizard"}
|
final String[] types = s.equals("Party") ? new String[]{"Cleric","Rogue","Warrior","Wizard"}
|
||||||
: s.split(" & ");
|
: s.split(" & ");
|
||||||
for (final String type : types) {
|
for (final String type : types) {
|
||||||
CardCollection valids = CardLists.filter(pChoices, CardPredicates.isType(type));
|
CardCollection valids = CardLists.filter(pChoices, CardPredicates.isType(type));
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package forge.game.ability.effects;
|
package forge.game.ability.effects;
|
||||||
|
|
||||||
|
import forge.card.ColorSet;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.deck.DeckRecognizer;
|
import forge.deck.DeckRecognizer;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.SpellAbilityEffect;
|
import forge.game.ability.SpellAbilityEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardUtil;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
@@ -41,6 +44,13 @@ public class ChooseColorEffect extends SpellAbilityEffect {
|
|||||||
String[] restrictedChoices = sa.getParam("Choices").split(",");
|
String[] restrictedChoices = sa.getParam("Choices").split(",");
|
||||||
colorChoices = Arrays.asList(restrictedChoices);
|
colorChoices = Arrays.asList(restrictedChoices);
|
||||||
}
|
}
|
||||||
|
if (sa.hasParam("ColorsFrom")) {
|
||||||
|
ColorSet cs = CardUtil.getColorsFromCards(AbilityUtils.getDefinedCards(card, sa.getParam("ColorsFrom"), sa));
|
||||||
|
if (cs.isColorless()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
colorChoices = cs.stream().map(Object::toString).collect(Collectors.toCollection(ArrayList::new));
|
||||||
|
}
|
||||||
if (sa.hasParam("Exclude")) {
|
if (sa.hasParam("Exclude")) {
|
||||||
for (String s : sa.getParam("Exclude").split(",")) {
|
for (String s : sa.getParam("Exclude").split(",")) {
|
||||||
colorChoices.remove(s);
|
colorChoices.remove(s);
|
||||||
@@ -49,24 +59,22 @@ public class ChooseColorEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
for (Player p : getTargetPlayers(sa)) {
|
for (Player p : getTargetPlayers(sa)) {
|
||||||
if (!p.isInGame()) {
|
if (!p.isInGame()) {
|
||||||
p = getNewChooser(sa, sa.getActivatingPlayer(), p);
|
p = getNewChooser(sa, p);
|
||||||
}
|
}
|
||||||
List<String> chosenColors = new ArrayList<>();
|
List<String> chosenColors = new ArrayList<>();
|
||||||
int cntMin = sa.hasParam("TwoColors") ? 2 : 1;
|
int cntMin = sa.hasParam("UpTo") ? 0 : sa.hasParam("TwoColors") ? 2 : 1;
|
||||||
int cntMax = sa.hasParam("TwoColors") ? 2 : sa.hasParam("OrColors") ? colorChoices.size() : 1;
|
int cntMax = sa.hasParam("TwoColors") ? 2 : sa.hasParam("OrColors") ? colorChoices.size() : 1;
|
||||||
String prompt = null;
|
String prompt = null;
|
||||||
if (cntMax == 1) {
|
if (cntMax == 1) {
|
||||||
prompt = Localizer.getInstance().getMessage("lblChooseAColor");
|
prompt = Localizer.getInstance().getMessage("lblChooseAColor");
|
||||||
} else {
|
} else if (cntMax > cntMin) {
|
||||||
if (cntMax > cntMin) {
|
if (cntMax >= MagicColor.NUMBER_OR_COLORS) {
|
||||||
if (cntMax >= MagicColor.NUMBER_OR_COLORS) {
|
prompt = Localizer.getInstance().getMessage("lblAtLastChooseNumColors", Lang.getNumeral(cntMin));
|
||||||
prompt = Localizer.getInstance().getMessage("lblAtLastChooseNumColors", Lang.getNumeral(cntMin));
|
|
||||||
} else {
|
|
||||||
prompt = Localizer.getInstance().getMessage("lblChooseSpecifiedRangeColors", Lang.getNumeral(cntMin), Lang.getNumeral(cntMax));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
prompt = Localizer.getInstance().getMessage("lblChooseNColors", Lang.getNumeral(cntMax));
|
prompt = Localizer.getInstance().getMessage("lblChooseSpecifiedRangeColors", Lang.getNumeral(cntMin), Lang.getNumeral(cntMax));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
prompt = Localizer.getInstance().getMessage("lblChooseNColors", Lang.getNumeral(cntMax));
|
||||||
}
|
}
|
||||||
Player noNotify = p;
|
Player noNotify = p;
|
||||||
if (sa.hasParam("Random")) {
|
if (sa.hasParam("Random")) {
|
||||||
|
|||||||
@@ -62,23 +62,22 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
for (Player p : getDefinedPlayersOrTargeted(sa)) {
|
for (Player p : getDefinedPlayersOrTargeted(sa)) {
|
||||||
if (!p.isInGame()) {
|
if (!p.isInGame()) {
|
||||||
p = getNewChooser(sa, sa.getActivatingPlayer(), p);
|
p = getNewChooser(sa, p);
|
||||||
}
|
}
|
||||||
// determine if any of the choices are not valid
|
|
||||||
List<SpellAbility> saToRemove = Lists.newArrayList();
|
|
||||||
|
|
||||||
|
// determine if any of the choices are not valid
|
||||||
|
List<SpellAbility> availableSA = Lists.newArrayList(abilities);
|
||||||
for (SpellAbility saChoice : abilities) {
|
for (SpellAbility saChoice : abilities) {
|
||||||
if (saChoice.getRestrictions() != null && !saChoice.getRestrictions().checkOtherRestrictions(host, saChoice, sa.getActivatingPlayer())) {
|
if (saChoice.getRestrictions() != null && !saChoice.getRestrictions().checkOtherRestrictions(host, saChoice, sa.getActivatingPlayer())) {
|
||||||
saToRemove.add(saChoice);
|
availableSA.remove(saChoice);
|
||||||
} else if (saChoice.hasParam("UnlessCost")) {
|
} else if (saChoice.hasParam("UnlessCost")) {
|
||||||
// generic check for if the cost can be paid
|
// generic check for if the cost can be paid
|
||||||
Cost unlessCost = new Cost(saChoice.getParam("UnlessCost"), false);
|
Cost unlessCost = new Cost(saChoice.getParam("UnlessCost"), false);
|
||||||
if (!unlessCost.canPay(sa, p, true)) {
|
if (!unlessCost.canPay(sa, p, true)) {
|
||||||
saToRemove.add(saChoice);
|
availableSA.remove(saChoice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
abilities.removeAll(saToRemove);
|
|
||||||
|
|
||||||
List<SpellAbility> chosenSAs = Lists.newArrayList();
|
List<SpellAbility> chosenSAs = Lists.newArrayList();
|
||||||
String prompt = sa.getParamOrDefault("ChoicePrompt", "Choose");
|
String prompt = sa.getParamOrDefault("ChoicePrompt", "Choose");
|
||||||
@@ -86,7 +85,7 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
if (sa.hasParam("AtRandom")) {
|
if (sa.hasParam("AtRandom")) {
|
||||||
random = true;
|
random = true;
|
||||||
chosenSAs = Aggregates.random(abilities, amount);
|
chosenSAs = Aggregates.random(availableSA, amount);
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (sa.getParam("AtRandom").equals("Urza") && i < chosenSAs.size()) {
|
while (sa.getParam("AtRandom").equals("Urza") && i < chosenSAs.size()) {
|
||||||
@@ -99,8 +98,8 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
|||||||
chosenSAs.set(i, Aggregates.random(abilities));
|
chosenSAs.set(i, Aggregates.random(abilities));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (!abilities.isEmpty()) {
|
} else if (!availableSA.isEmpty()) {
|
||||||
chosenSAs = p.getController().chooseSpellAbilitiesForEffect(abilities, sa, prompt, amount, ImmutableMap.of());
|
chosenSAs = p.getController().chooseSpellAbilitiesForEffect(availableSA, sa, prompt, amount, ImmutableMap.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Object> oldRem = Lists.newArrayList(IterableUtil.filter(host.getRemembered(), Player.class));
|
List<Object> oldRem = Lists.newArrayList(IterableUtil.filter(host.getRemembered(), Player.class));
|
||||||
@@ -117,7 +116,6 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
|||||||
} else if (secretly) {
|
} else if (secretly) {
|
||||||
if (record.length() > 0) record.append("\r\n");
|
if (record.length() > 0) record.append("\r\n");
|
||||||
record.append(Localizer.getInstance().getMessage("lblPlayerChooseValue", p, chosenValue));
|
record.append(Localizer.getInstance().getMessage("lblPlayerChooseValue", p, chosenValue));
|
||||||
|
|
||||||
}
|
}
|
||||||
if (sa.hasParam("SetChosenMode")) {
|
if (sa.hasParam("SetChosenMode")) {
|
||||||
sa.getHostCard().setChosenMode(chosenValue);
|
sa.getHostCard().setChosenMode(chosenValue);
|
||||||
|
|||||||
@@ -129,6 +129,8 @@ public class CloneEffect extends SpellAbilityEffect {
|
|||||||
cloneTargets.remove(cardToCopy);
|
cloneTargets.remove(cardToCopy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final long ts = game.getNextTimestamp();
|
||||||
|
|
||||||
for (Card tgtCard : cloneTargets) {
|
for (Card tgtCard : cloneTargets) {
|
||||||
if (sa.hasParam("CloneZone") &&
|
if (sa.hasParam("CloneZone") &&
|
||||||
!tgtCard.isInZone(ZoneType.smartValueOf(sa.getParam("CloneZone")))) {
|
!tgtCard.isInZone(ZoneType.smartValueOf(sa.getParam("CloneZone")))) {
|
||||||
@@ -141,7 +143,6 @@ public class CloneEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
game.getTriggerHandler().clearActiveTriggers(tgtCard, null);
|
game.getTriggerHandler().clearActiveTriggers(tgtCard, null);
|
||||||
|
|
||||||
final long ts = game.getNextTimestamp();
|
|
||||||
tgtCard.addCloneState(CardFactory.getCloneStates(cardToCopy, tgtCard, sa), ts);
|
tgtCard.addCloneState(CardFactory.getCloneStates(cardToCopy, tgtCard, sa), ts);
|
||||||
tgtCard.updateRooms();
|
tgtCard.updateRooms();
|
||||||
|
|
||||||
@@ -199,7 +200,7 @@ public class CloneEffect extends SpellAbilityEffect {
|
|||||||
tgtCard.addRemembered(cardToCopy);
|
tgtCard.addRemembered(cardToCopy);
|
||||||
}
|
}
|
||||||
// spire
|
// spire
|
||||||
tgtCard.setChosenColorID(cardToCopy.getChosenColorID());
|
tgtCard.setMarkedColors(cardToCopy.getMarkedColors());
|
||||||
|
|
||||||
game.fireEvent(new GameEventCardStatsChanged(tgtCard));
|
game.fireEvent(new GameEventCardStatsChanged(tgtCard));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,10 +132,6 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
|||||||
tgtCards = getDefinedCards(sa);
|
tgtCards = getDefinedCards(sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tgtCards != null & sa.hasParam("ControlledByTarget")) {
|
|
||||||
tgtCards = CardLists.filterControlledBy(tgtCards, getTargetPlayers(sa));
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for lose control criteria right away
|
// check for lose control criteria right away
|
||||||
if (lose != null && lose.contains("LeavesPlay") && !source.isInPlay()) {
|
if (lose != null && lose.contains("LeavesPlay") && !source.isInPlay()) {
|
||||||
return;
|
return;
|
||||||
@@ -170,7 +166,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
|||||||
tgtC.addTempController(newController, tStamp);
|
tgtC.addTempController(newController, tStamp);
|
||||||
|
|
||||||
if (bUntap) {
|
if (bUntap) {
|
||||||
if (tgtC.untap(true)) untapped.add(tgtC);
|
if (tgtC.untap()) untapped.add(tgtC);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keywords != null) {
|
if (keywords != null) {
|
||||||
|
|||||||
@@ -26,10 +26,8 @@ public class ControlPlayerEffect extends SpellAbilityEffect {
|
|||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
@Override
|
@Override
|
||||||
public void resolve(SpellAbility sa) {
|
public void resolve(SpellAbility sa) {
|
||||||
final Player activator = sa.getActivatingPlayer();
|
final Player controller = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Controller"), sa).get(0);
|
||||||
final Game game = activator.getGame();
|
final Game game = controller.getGame();
|
||||||
final Player controller = sa.hasParam("Controller") ? AbilityUtils.getDefinedPlayers(
|
|
||||||
sa.getHostCard(), sa.getParam("Controller"), sa).get(0) : activator;
|
|
||||||
|
|
||||||
for (final Player pTarget: getTargetPlayers(sa)) {
|
for (final Player pTarget: getTargetPlayers(sa)) {
|
||||||
// before next untap gain control
|
// before next untap gain control
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import forge.game.player.PlayerActionConfirmMode;
|
|||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.item.PaperCard;
|
import forge.item.PaperCard;
|
||||||
import forge.util.PredicateString.StringOp;
|
|
||||||
|
|
||||||
public class CopyPermanentEffect extends TokenEffectBase {
|
public class CopyPermanentEffect extends TokenEffectBase {
|
||||||
|
|
||||||
@@ -185,19 +184,15 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
|||||||
System.err.println("Copying random permanent(s): " + tgtCards.toString());
|
System.err.println("Copying random permanent(s): " + tgtCards.toString());
|
||||||
}
|
}
|
||||||
} else if (sa.hasParam("DefinedName")) {
|
} else if (sa.hasParam("DefinedName")) {
|
||||||
List<PaperCard> cards = Lists.newArrayList(StaticData.instance().getCommonCards().getUniqueCards());
|
|
||||||
String name = sa.getParam("DefinedName");
|
String name = sa.getParam("DefinedName");
|
||||||
if (name.equals("NamedCard")) {
|
if (name.equals("NamedCard")) {
|
||||||
if (!host.getNamedCard().isEmpty()) {
|
if (!host.getNamedCard().isEmpty()) {
|
||||||
name = host.getNamedCard();
|
name = host.getNamedCard();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PaperCard pc = StaticData.instance().getCommonCards().getUniqueByName(name);
|
||||||
Predicate<PaperCard> cpp = PaperCardPredicates.fromRules(CardRulesPredicates.name(StringOp.EQUALS, name));
|
if (pc != null) {
|
||||||
cards = Lists.newArrayList(IterableUtil.filter(cards, cpp));
|
tgtCards.add(Card.fromPaperCard(pc, controller));
|
||||||
|
|
||||||
if (!cards.isEmpty()) {
|
|
||||||
tgtCards.add(Card.fromPaperCard(cards.get(0), controller));
|
|
||||||
}
|
}
|
||||||
} else if (sa.hasParam("Choices")) {
|
} else if (sa.hasParam("Choices")) {
|
||||||
Player chooser = activator;
|
Player chooser = activator;
|
||||||
@@ -314,7 +309,7 @@ public class CopyPermanentEffect extends TokenEffectBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// spire
|
// spire
|
||||||
copy.setChosenColorID(original.getChosenColorID());
|
copy.setMarkedColors(original.getMarkedColors());
|
||||||
|
|
||||||
copy.setTokenSpawningAbility(sa);
|
copy.setTokenSpawningAbility(sa);
|
||||||
copy.setGamePieceType(GamePieceType.TOKEN);
|
copy.setGamePieceType(GamePieceType.TOKEN);
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import forge.game.card.CardFactory;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.replacement.ReplacementType;
|
import forge.game.replacement.ReplacementType;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.staticability.StaticAbilityCantBeCopied;
|
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.*;
|
import forge.util.*;
|
||||||
import forge.util.collect.FCollection;
|
import forge.util.collect.FCollection;
|
||||||
@@ -66,7 +65,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
List<SpellAbility> tgtSpells = getTargetSpells(sa);
|
List<SpellAbility> tgtSpells = getTargetSpells(sa);
|
||||||
|
|
||||||
tgtSpells.removeIf(tgtSA -> StaticAbilityCantBeCopied.cantBeCopied(tgtSA.getHostCard()));
|
tgtSpells.removeIf(SpellAbility::cantBeCopied);
|
||||||
|
|
||||||
if (tgtSpells.isEmpty() || amount == 0) {
|
if (tgtSpells.isEmpty() || amount == 0) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.google.common.collect.Lists;
|
|||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
|
import forge.card.MagicColor;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.GameEntityCounterTable;
|
import forge.game.GameEntityCounterTable;
|
||||||
@@ -631,7 +632,19 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resolvePerType(sa, placer, counterType, counterAmount, table, true);
|
if (sa.hasParam("ForColor")) {
|
||||||
|
Iterable<String> oldColors = card.getChosenColors();
|
||||||
|
for (String color : MagicColor.Constant.ONLY_COLORS) {
|
||||||
|
card.setChosenColors(Lists.newArrayList(color));
|
||||||
|
if (sa.getOriginalParam("ChoiceTitle") != null) {
|
||||||
|
sa.getMapParams().put("ChoiceTitle", sa.getOriginalParam("ChoiceTitle").replace("chosenColor", color));
|
||||||
|
}
|
||||||
|
resolvePerType(sa, placer, counterType, counterAmount, table, true);
|
||||||
|
}
|
||||||
|
card.setChosenColors(Lists.newArrayList(oldColors));
|
||||||
|
} else {
|
||||||
|
resolvePerType(sa, placer, counterType, counterAmount, table, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table.replaceCounterEffect(game, sa, true);
|
table.replaceCounterEffect(game, sa, true);
|
||||||
|
|||||||
@@ -65,8 +65,7 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
|
|||||||
ctype = CounterType.getType(sa.getParam("CounterType"));
|
ctype = CounterType.getType(sa.getParam("CounterType"));
|
||||||
}
|
}
|
||||||
|
|
||||||
final Player pl = !sa.hasParam("DefinedPlayer") ? sa.getActivatingPlayer() :
|
final Player pl = AbilityUtils.getDefinedPlayers(source, sa.getParam("DefinedPlayer"), sa).getFirst();
|
||||||
AbilityUtils.getDefinedPlayers(source, sa.getParam("DefinedPlayer"), sa).getFirst();
|
|
||||||
final boolean eachExisting = sa.hasParam("EachExistingCounter");
|
final boolean eachExisting = sa.hasParam("EachExistingCounter");
|
||||||
|
|
||||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||||
@@ -79,7 +78,7 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
|
|||||||
if (gameCard == null || !tgtCard.equalsWithGameTimestamp(gameCard)) {
|
if (gameCard == null || !tgtCard.equalsWithGameTimestamp(gameCard)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!eachExisting && sa.hasParam("Optional") && !pl.getController().confirmAction(sa, null,
|
if (sa.hasParam("Optional") && !pl.getController().confirmAction(sa, null,
|
||||||
Localizer.getInstance().getMessage("lblWouldYouLikePutRemoveCounters", ctype.getName(),
|
Localizer.getInstance().getMessage("lblWouldYouLikePutRemoveCounters", ctype.getName(),
|
||||||
CardTranslation.getTranslatedName(gameCard.getName())), null)) {
|
CardTranslation.getTranslatedName(gameCard.getName())), null)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -114,8 +113,6 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
|
|||||||
String prompt = Localizer.getInstance().getMessage("lblSelectCounterTypeToAddOrRemove");
|
String prompt = Localizer.getInstance().getMessage("lblSelectCounterTypeToAddOrRemove");
|
||||||
CounterType chosenType = pc.chooseCounterType(list, sa, prompt, params);
|
CounterType chosenType = pc.chooseCounterType(list, sa, prompt, params);
|
||||||
|
|
||||||
params.put("CounterType", chosenType);
|
|
||||||
prompt = Localizer.getInstance().getMessage("lblWhatToDoWithTargetCounter", chosenType.getName(), CardTranslation.getTranslatedName(tgtCard.getName())) + " ";
|
|
||||||
boolean putCounter;
|
boolean putCounter;
|
||||||
if (sa.hasParam("RemoveConditionSVar")) {
|
if (sa.hasParam("RemoveConditionSVar")) {
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
@@ -137,6 +134,8 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
|
|||||||
} else if (!canReceive && canRemove) {
|
} else if (!canReceive && canRemove) {
|
||||||
putCounter = false;
|
putCounter = false;
|
||||||
} else {
|
} else {
|
||||||
|
params.put("CounterType", chosenType);
|
||||||
|
prompt = Localizer.getInstance().getMessage("lblWhatToDoWithTargetCounter", chosenType.getName(), CardTranslation.getTranslatedName(tgtCard.getName())) + " ";
|
||||||
putCounter = pc.chooseBinary(sa, prompt, BinaryChoiceType.AddOrRemove, params);
|
putCounter = pc.chooseBinary(sa, prompt, BinaryChoiceType.AddOrRemove, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,34 +101,12 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
|||||||
boolean rememberAmount = sa.hasParam("RememberAmount");
|
boolean rememberAmount = sa.hasParam("RememberAmount");
|
||||||
|
|
||||||
int totalRemoved = 0;
|
int totalRemoved = 0;
|
||||||
|
|
||||||
for (final Player tgtPlayer : getTargetPlayers(sa)) {
|
|
||||||
if (!tgtPlayer.isInGame()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Removing energy
|
|
||||||
if (type.equals("All")) {
|
|
||||||
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(tgtPlayer.getCounters().entrySet())) {
|
|
||||||
totalRemoved += tgtPlayer.subtractCounter(e.getKey(), e.getValue(), activator);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (num.equals("All")) {
|
|
||||||
cntToRemove = tgtPlayer.getCounters(counterType);
|
|
||||||
}
|
|
||||||
if (type.equals("Any")) {
|
|
||||||
totalRemoved += removeAnyType(tgtPlayer, cntToRemove, sa);
|
|
||||||
} else {
|
|
||||||
totalRemoved += tgtPlayer.subtractCounter(counterType, cntToRemove, activator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CardCollectionView srcCards;
|
CardCollectionView srcCards;
|
||||||
|
|
||||||
String typeforPrompt = counterType == null ? "" : counterType.getName();
|
String typeforPrompt = counterType == null ? "" : counterType.getName();
|
||||||
String title = Localizer.getInstance().getMessage("lblChooseCardsToTakeTargetCounters", typeforPrompt);
|
String title = Localizer.getInstance().getMessage("lblChooseCardsToTakeTargetCounters", typeforPrompt);
|
||||||
title = title.replace(" ", " ");
|
title = title.replace(" ", " ");
|
||||||
if (sa.hasParam("Choices") && counterType != null) {
|
if (sa.hasParam("Choices")) {
|
||||||
ZoneType choiceZone = sa.hasParam("ChoiceZone") ? ZoneType.smartValueOf(sa.getParam("ChoiceZone"))
|
ZoneType choiceZone = sa.hasParam("ChoiceZone") ? ZoneType.smartValueOf(sa.getParam("ChoiceZone"))
|
||||||
: ZoneType.Battlefield;
|
: ZoneType.Battlefield;
|
||||||
|
|
||||||
@@ -145,6 +123,27 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
|||||||
params.put("CounterType", counterType);
|
params.put("CounterType", counterType);
|
||||||
srcCards = pc.chooseCardsForEffect(choices, sa, title, min, max, min == 0, params);
|
srcCards = pc.chooseCardsForEffect(choices, sa, title, min, max, min == 0, params);
|
||||||
} else {
|
} else {
|
||||||
|
for (final Player tgtPlayer : getTargetPlayers(sa)) {
|
||||||
|
if (!tgtPlayer.isInGame()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Removing energy
|
||||||
|
if (type.equals("All")) {
|
||||||
|
for (Map.Entry<CounterType, Integer> e : Lists.newArrayList(tgtPlayer.getCounters().entrySet())) {
|
||||||
|
totalRemoved += tgtPlayer.subtractCounter(e.getKey(), e.getValue(), activator);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (num.equals("All")) {
|
||||||
|
cntToRemove = tgtPlayer.getCounters(counterType);
|
||||||
|
}
|
||||||
|
if (type.equals("Any")) {
|
||||||
|
totalRemoved += removeAnyType(tgtPlayer, cntToRemove, sa);
|
||||||
|
} else {
|
||||||
|
totalRemoved += tgtPlayer.subtractCounter(counterType, cntToRemove, activator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
srcCards = getTargetCards(sa);
|
srcCards = getTargetCards(sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ public class DamageEachEffect extends DamageBaseEffect {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.abilityfactory.SpellEffect#resolve(java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellEffect#resolve(java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -127,8 +127,8 @@ public class DigEffect extends SpellAbilityEffect {
|
|||||||
final boolean skipReorder = sa.hasParam("SkipReorder");
|
final boolean skipReorder = sa.hasParam("SkipReorder");
|
||||||
|
|
||||||
// A hack for cards like Explorer's Scope that need to ensure that a card is revealed to the player activating the ability
|
// A hack for cards like Explorer's Scope that need to ensure that a card is revealed to the player activating the ability
|
||||||
final boolean forceReveal = sa.hasParam("ForceRevealToController") ||
|
final boolean forceReveal = sa.hasParam("ForceRevealToController")
|
||||||
sa.hasParam("ForceReveal");
|
|| sa.hasParam("ForceReveal") || sa.hasParam("WithMayLook");
|
||||||
|
|
||||||
// These parameters are used to indicate that a dialog box must be show to the player asking if the player wants to proceed
|
// These parameters are used to indicate that a dialog box must be show to the player asking if the player wants to proceed
|
||||||
// with an optional ability, otherwise the optional ability is skipped.
|
// with an optional ability, otherwise the optional ability is skipped.
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package forge.game.ability.effects;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
@@ -124,11 +123,9 @@ public class DiscardEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
final List<Player> targets = getTargetPlayers(sa),
|
final List<Player> targets = getTargetPlayers(sa),
|
||||||
discarders;
|
discarders;
|
||||||
Player firstTarget = null;
|
|
||||||
if (mode.equals("RevealTgtChoose")) {
|
if (mode.equals("RevealTgtChoose")) {
|
||||||
// In this case the target need not be the discarding player
|
// In this case the target need not be the discarding player
|
||||||
discarders = getDefinedPlayersOrTargeted(sa);
|
discarders = getDefinedPlayersOrTargeted(sa);
|
||||||
firstTarget = Iterables.getFirst(targets, null);
|
|
||||||
} else {
|
} else {
|
||||||
discarders = targets;
|
discarders = targets;
|
||||||
}
|
}
|
||||||
@@ -140,125 +137,123 @@ public class DiscardEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CardCollectionView toBeDiscarded = new CardCollection();
|
CardCollectionView toBeDiscarded = new CardCollection();
|
||||||
if ((mode.equals("RevealTgtChoose") && firstTarget != null) || !sa.usesTargeting() || p.canBeTargetedBy(sa)) {
|
final int numCardsInHand = p.getCardsIn(ZoneType.Hand).size();
|
||||||
final int numCardsInHand = p.getCardsIn(ZoneType.Hand).size();
|
if (mode.equals("Defined")) {
|
||||||
if (mode.equals("Defined")) {
|
if (!p.canDiscardBy(sa, true)) {
|
||||||
if (!p.canDiscardBy(sa, true)) {
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean runDiscard = !sa.hasParam("Optional")
|
|
||||||
|| p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, sa.getParam("DiscardMessage"), null);
|
|
||||||
if (runDiscard) {
|
|
||||||
toBeDiscarded = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedCards"), sa);
|
|
||||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode.equals("Hand")) {
|
boolean runDiscard = !sa.hasParam("Optional")
|
||||||
toBeDiscarded = p.getCardsIn(ZoneType.Hand);
|
|| p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, sa.getParam("DiscardMessage"), null);
|
||||||
|
if (runDiscard) {
|
||||||
// Empty hand can still be discarded
|
toBeDiscarded = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedCards"), sa);
|
||||||
if (!toBeDiscarded.isEmpty() && !p.canDiscardBy(sa, true)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int numCards = 1;
|
if (mode.equals("Hand")) {
|
||||||
if (sa.hasParam("NumCards")) {
|
toBeDiscarded = p.getCardsIn(ZoneType.Hand);
|
||||||
numCards = AbilityUtils.calculateAmount(source, sa.getParam("NumCards"), sa);
|
|
||||||
numCards = Math.min(numCards, numCardsInHand);
|
// Empty hand can still be discarded
|
||||||
|
if (!toBeDiscarded.isEmpty() && !p.canDiscardBy(sa, true)) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode.equals("Random")) {
|
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||||
if (!p.canDiscardBy(sa, true)) {
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String message = Localizer.getInstance().getMessage("lblWouldYouLikeRandomDiscardTargetCard", String.valueOf(numCards));
|
|
||||||
boolean runDiscard = !sa.hasParam("Optional") || p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, message, null);
|
|
||||||
|
|
||||||
if (runDiscard) {
|
int numCards = 1;
|
||||||
final String valid = sa.getParamOrDefault("DiscardValid", "Card");
|
if (sa.hasParam("NumCards")) {
|
||||||
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), valid, source.getController(), source, sa);
|
numCards = AbilityUtils.calculateAmount(source, sa.getParam("NumCards"), sa);
|
||||||
|
numCards = Math.min(numCards, numCardsInHand);
|
||||||
|
}
|
||||||
|
|
||||||
toBeDiscarded = new CardCollection(Aggregates.random(list, numCards));
|
if (mode.equals("Random")) {
|
||||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
if (!p.canDiscardBy(sa, true)) {
|
||||||
}
|
continue;
|
||||||
}
|
}
|
||||||
else if (mode.equals("TgtChoose") && sa.hasParam("UnlessType")) {
|
String message = Localizer.getInstance().getMessage("lblWouldYouLikeRandomDiscardTargetCard", String.valueOf(numCards));
|
||||||
if (!p.canDiscardBy(sa, true)) {
|
boolean runDiscard = !sa.hasParam("Optional") || p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, message, null);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (numCardsInHand > 0) {
|
|
||||||
CardCollectionView hand = p.getCardsIn(ZoneType.Hand);
|
|
||||||
toBeDiscarded = p.getController().chooseCardsToDiscardUnlessType(Math.min(numCards, numCardsInHand), hand, sa.getParam("UnlessType"), sa);
|
|
||||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game,toBeDiscarded, ZoneType.Graveyard, sa);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (mode.equals("RevealDiscardAll")) {
|
|
||||||
// Reveal
|
|
||||||
final CardCollectionView dPHand = p.getCardsIn(ZoneType.Hand);
|
|
||||||
|
|
||||||
for (final Player opp : p.getAllOtherPlayers()) {
|
|
||||||
opp.getController().reveal(dPHand, ZoneType.Hand, p, Localizer.getInstance().getMessage("lblReveal") + " ");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!p.canDiscardBy(sa, true)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String valid = sa.getParamOrDefault("DiscardValid", "Card");
|
|
||||||
|
|
||||||
if (valid.contains("X")) {
|
|
||||||
valid = TextUtil.fastReplace(valid,
|
|
||||||
"X", Integer.toString(AbilityUtils.calculateAmount(source, "X", sa)));
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeDiscarded = CardLists.getValidCards(dPHand, valid, source.getController(), source, sa);
|
|
||||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
|
||||||
} else if (mode.endsWith("YouChoose") || mode.endsWith("TgtChoose")) {
|
|
||||||
CardCollectionView dPHand = p.getCardsIn(ZoneType.Hand);
|
|
||||||
if (dPHand.isEmpty())
|
|
||||||
continue; // for loop over players
|
|
||||||
|
|
||||||
if (sa.hasParam("RevealNumber")) {
|
|
||||||
int amount = AbilityUtils.calculateAmount(source, sa.getParam("RevealNumber"), sa);
|
|
||||||
dPHand = p.getController().chooseCardsToRevealFromHand(amount, amount, dPHand);
|
|
||||||
}
|
|
||||||
|
|
||||||
Player chooser = p;
|
|
||||||
if (mode.endsWith("YouChoose")) {
|
|
||||||
chooser = source.getController();
|
|
||||||
} else if (mode.equals("RevealTgtChoose")) {
|
|
||||||
chooser = firstTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode.startsWith("Reveal")) {
|
|
||||||
game.getAction().reveal(dPHand, p);
|
|
||||||
}
|
|
||||||
if (mode.startsWith("Look") && p != chooser) {
|
|
||||||
game.getAction().revealTo(dPHand, chooser);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!p.canDiscardBy(sa, true)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (runDiscard) {
|
||||||
final String valid = sa.getParamOrDefault("DiscardValid", "Card");
|
final String valid = sa.getParamOrDefault("DiscardValid", "Card");
|
||||||
CardCollection validCards = CardLists.getValidCards(dPHand, valid, source.getController(), source, sa);
|
List<Card> list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), valid, source.getController(), source, sa);
|
||||||
|
|
||||||
int min = sa.hasParam("AnyNumber") || sa.hasParam("Optional") ? 0 : Math.min(validCards.size(), numCards);
|
|
||||||
int max = sa.hasParam("AnyNumber") ? validCards.size() : Math.min(validCards.size(), numCards);
|
|
||||||
|
|
||||||
toBeDiscarded = max == 0 ? CardCollection.EMPTY : chooser.getController().chooseCardsToDiscardFrom(p, sa, validCards, min, max);
|
|
||||||
|
|
||||||
|
toBeDiscarded = new CardCollection(Aggregates.random(list, numCards));
|
||||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode.equals("TgtChoose") && sa.hasParam("UnlessType")) {
|
||||||
|
if (!p.canDiscardBy(sa, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (numCardsInHand > 0) {
|
||||||
|
CardCollectionView hand = p.getCardsIn(ZoneType.Hand);
|
||||||
|
toBeDiscarded = p.getController().chooseCardsToDiscardUnlessType(Math.min(numCards, numCardsInHand), hand, sa.getParam("UnlessType"), sa);
|
||||||
|
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game,toBeDiscarded, ZoneType.Graveyard, sa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode.equals("RevealDiscardAll")) {
|
||||||
|
// Reveal
|
||||||
|
final CardCollectionView dPHand = p.getCardsIn(ZoneType.Hand);
|
||||||
|
|
||||||
if (mode.startsWith("Reveal") && p != chooser) {
|
for (final Player opp : p.getAllOtherPlayers()) {
|
||||||
p.getController().reveal(toBeDiscarded, ZoneType.Hand, p, Localizer.getInstance().getMessage("lblPlayerHasChosenCardsFrom", chooser.getName()));
|
opp.getController().reveal(dPHand, ZoneType.Hand, p, Localizer.getInstance().getMessage("lblReveal") + " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!p.canDiscardBy(sa, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String valid = sa.getParamOrDefault("DiscardValid", "Card");
|
||||||
|
|
||||||
|
if (valid.contains("X")) {
|
||||||
|
valid = TextUtil.fastReplace(valid,
|
||||||
|
"X", Integer.toString(AbilityUtils.calculateAmount(source, "X", sa)));
|
||||||
|
}
|
||||||
|
|
||||||
|
toBeDiscarded = CardLists.getValidCards(dPHand, valid, source.getController(), source, sa);
|
||||||
|
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||||
|
} else if (mode.endsWith("YouChoose") || mode.endsWith("TgtChoose")) {
|
||||||
|
CardCollectionView dPHand = p.getCardsIn(ZoneType.Hand);
|
||||||
|
if (dPHand.isEmpty())
|
||||||
|
continue; // for loop over players
|
||||||
|
|
||||||
|
if (sa.hasParam("RevealNumber")) {
|
||||||
|
int amount = AbilityUtils.calculateAmount(source, sa.getParam("RevealNumber"), sa);
|
||||||
|
dPHand = p.getController().chooseCardsToRevealFromHand(amount, amount, dPHand);
|
||||||
|
}
|
||||||
|
|
||||||
|
Player chooser = p;
|
||||||
|
if (mode.endsWith("YouChoose")) {
|
||||||
|
chooser = sa.getActivatingPlayer();
|
||||||
|
} else if (mode.equals("RevealTgtChoose")) {
|
||||||
|
chooser = targets.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.startsWith("Reveal")) {
|
||||||
|
game.getAction().reveal(dPHand, p);
|
||||||
|
}
|
||||||
|
if (mode.startsWith("Look") && p != chooser) {
|
||||||
|
game.getAction().revealTo(dPHand, chooser);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!p.canDiscardBy(sa, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String valid = sa.getParamOrDefault("DiscardValid", "Card");
|
||||||
|
CardCollection validCards = CardLists.getValidCards(dPHand, valid, source.getController(), source, sa);
|
||||||
|
|
||||||
|
int min = sa.hasParam("AnyNumber") || sa.hasParam("Optional") ? 0 : Math.min(validCards.size(), numCards);
|
||||||
|
int max = sa.hasParam("AnyNumber") ? validCards.size() : Math.min(validCards.size(), numCards);
|
||||||
|
|
||||||
|
toBeDiscarded = max == 0 ? CardCollection.EMPTY : chooser.getController().chooseCardsToDiscardFrom(p, sa, validCards, min, max);
|
||||||
|
|
||||||
|
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||||
|
|
||||||
|
if (mode.startsWith("Reveal") && p != chooser) {
|
||||||
|
p.getController().reveal(toBeDiscarded, ZoneType.Hand, p, Localizer.getInstance().getMessage("lblPlayerHasChosenCardsFrom", chooser.getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
discardedMap.put(p, toBeDiscarded);
|
discardedMap.put(p, toBeDiscarded);
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ import java.util.*;
|
|||||||
// Cardnames that include "," must use ";" instead in Spellbook$ (i.e. Tovolar; Dire Overlord)
|
// Cardnames that include "," must use ";" instead in Spellbook$ (i.e. Tovolar; Dire Overlord)
|
||||||
name = name.replace(";", ",");
|
name = name.replace(";", ",");
|
||||||
Card cardOption = Card.fromPaperCard(StaticData.instance().getCommonCards().getUniqueByName(name), player);
|
Card cardOption = Card.fromPaperCard(StaticData.instance().getCommonCards().getUniqueByName(name), player);
|
||||||
cardOption.setTokenCard(true);
|
cardOption.setTokenCard(sa.hasParam("TokenCard"));
|
||||||
draftOptions.add(cardOption);
|
draftOptions.add(cardOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class EndTurnEffect extends SpellAbilityEffect {
|
|||||||
final List<Player> enders = getDefinedPlayersOrTargeted(sa, "Defined");
|
final List<Player> enders = getDefinedPlayersOrTargeted(sa, "Defined");
|
||||||
Player ender = enders.isEmpty() ? sa.getActivatingPlayer() : enders.get(0);
|
Player ender = enders.isEmpty() ? sa.getActivatingPlayer() : enders.get(0);
|
||||||
if (!ender.isInGame()) {
|
if (!ender.isInGame()) {
|
||||||
ender = getNewChooser(sa, sa.getActivatingPlayer(), ender);
|
ender = getNewChooser(sa, ender);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.hasParam("Optional") && !ender.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantEndTurn"), null)) {
|
if (sa.hasParam("Optional") && !ender.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantEndTurn"), null)) {
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package forge.game.ability.effects;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.mutable.MutableBoolean;
|
||||||
|
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
import forge.game.Game;
|
||||||
|
import forge.game.GameActionUtil;
|
||||||
|
import forge.game.GameEntityCounterTable;
|
||||||
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardZoneTable;
|
||||||
|
import forge.game.card.CounterEnumType;
|
||||||
|
import forge.game.card.TokenCreateTable;
|
||||||
|
import forge.game.card.token.TokenInfo;
|
||||||
|
import forge.game.event.GameEventCombatChanged;
|
||||||
|
import forge.game.event.GameEventTokenCreated;
|
||||||
|
import forge.game.player.Player;
|
||||||
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
|
import forge.util.CardTranslation;
|
||||||
|
import forge.util.Lang;
|
||||||
|
import forge.util.Localizer;
|
||||||
|
|
||||||
|
public class EndureEffect extends TokenEffectBase {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getStackDescription(SpellAbility sa) {
|
||||||
|
final Card host = sa.getHostCard();
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
List<Card> tgt = getTargetCards(sa);
|
||||||
|
|
||||||
|
sb.append(Lang.joinHomogenous(tgt));
|
||||||
|
sb.append(" ");
|
||||||
|
sb.append(tgt.size() > 1 ? "endure" : "endures");
|
||||||
|
|
||||||
|
int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Num", "1"), sa);
|
||||||
|
|
||||||
|
sb.append(" ").append(amount);
|
||||||
|
sb.append(". ");
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resolve(SpellAbility sa) {
|
||||||
|
final Card host = sa.getHostCard();
|
||||||
|
final Game game = host.getGame();
|
||||||
|
String num = sa.getParamOrDefault("Num", "1");
|
||||||
|
int amount = AbilityUtils.calculateAmount(host, num, sa);
|
||||||
|
|
||||||
|
if (amount < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||||
|
TokenCreateTable tokenTable = new TokenCreateTable();
|
||||||
|
for (final Card c : GameActionUtil.orderCardsByTheirOwners(game, getTargetCards(sa), ZoneType.Battlefield, sa)) {
|
||||||
|
final Player pl = c.getController();
|
||||||
|
|
||||||
|
Card gamec = game.getCardState(c, null);
|
||||||
|
|
||||||
|
Map<String, Object> params = Maps.newHashMap();
|
||||||
|
params.put("RevealedCard", c);
|
||||||
|
params.put("Amount", amount);
|
||||||
|
if (gamec != null && gamec.isInPlay() && gamec.equalsWithGameTimestamp(c) && gamec.canReceiveCounters(CounterEnumType.P1P1)
|
||||||
|
&& pl.getController().confirmAction(sa, null,
|
||||||
|
Localizer.getInstance().getMessage("lblEndureAction", CardTranslation.getTranslatedName(c.getName()), amount),
|
||||||
|
gamec, params)) {
|
||||||
|
gamec.addCounter(CounterEnumType.P1P1, amount, pl, table);
|
||||||
|
} else {
|
||||||
|
final Card result = TokenInfo.getProtoType("w_x_x_spirit", sa, pl, false);
|
||||||
|
|
||||||
|
// set PT
|
||||||
|
result.setBasePowerString(num);
|
||||||
|
result.setBasePower(amount);
|
||||||
|
result.setBaseToughnessString(num);
|
||||||
|
result.setBaseToughness(amount);
|
||||||
|
|
||||||
|
tokenTable.put(pl, result, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.replaceCounterEffect(game, sa, true);
|
||||||
|
|
||||||
|
if (!tokenTable.isEmpty()) {
|
||||||
|
CardZoneTable triggerList = new CardZoneTable();
|
||||||
|
MutableBoolean combatChanged = new MutableBoolean(false);
|
||||||
|
makeTokenTable(tokenTable, false, triggerList, combatChanged, sa);
|
||||||
|
|
||||||
|
triggerList.triggerChangesZoneAll(game, sa);
|
||||||
|
|
||||||
|
game.fireEvent(new GameEventTokenCreated());
|
||||||
|
|
||||||
|
if (combatChanged.isTrue()) {
|
||||||
|
game.updateCombatForView();
|
||||||
|
game.fireEvent(new GameEventCombatChanged());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import forge.game.player.Player;
|
|||||||
import forge.game.player.PlayerCollection;
|
import forge.game.player.PlayerCollection;
|
||||||
import forge.game.player.PlayerController;
|
import forge.game.player.PlayerController;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.staticability.StaticAbilityFlipCoinMod;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.util.Localizer;
|
import forge.util.Localizer;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
@@ -48,56 +49,26 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
|||||||
@Override
|
@Override
|
||||||
public void resolve(SpellAbility sa) {
|
public void resolve(SpellAbility sa) {
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
final Player player = host.getController();
|
|
||||||
int flipMultiplier = 1; // For multiple copies of Krark's Thumb
|
|
||||||
|
|
||||||
final List<Player> playersToFlip = AbilityUtils.getDefinedPlayers(host, sa.getParam("Flipper"), sa);
|
final List<Player> playersToFlip = AbilityUtils.getDefinedPlayers(host, sa.getParam("Flipper"), sa);
|
||||||
if (playersToFlip.isEmpty()) {
|
//final List<Player> caller = AbilityUtils.getDefinedPlayers(host, sa.getParam("Caller"), sa);
|
||||||
playersToFlip.add(sa.getActivatingPlayer());
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Player> caller = AbilityUtils.getDefinedPlayers(host, sa.getParam("Caller"), sa);
|
|
||||||
if (caller.isEmpty()) {
|
|
||||||
caller.add(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean noCall = sa.hasParam("NoCall");
|
final boolean noCall = sa.hasParam("NoCall");
|
||||||
final boolean forEachPlayer = sa.hasParam("ForEachPlayer");
|
final boolean forEachPlayer = sa.hasParam("ForEachPlayer");
|
||||||
String varName = sa.getParamOrDefault("SaveNumFlipsToSVar", "X");
|
String varName = sa.getParamOrDefault("SaveNumFlipsToSVar", "X");
|
||||||
boolean victory = false;
|
|
||||||
int amount = 1;
|
int amount = 1;
|
||||||
if (sa.hasParam("Amount")) {
|
if (sa.hasParam("Amount")) {
|
||||||
amount = AbilityUtils.calculateAmount(host, sa.getParam("Amount"), sa);
|
amount = AbilityUtils.calculateAmount(host, sa.getParam("Amount"), sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!noCall && !forEachPlayer && amount == 1) {
|
|
||||||
flipMultiplier = getFlipMultiplier(caller.get(0));
|
|
||||||
victory = flipCoinCall(caller.get(0), sa, flipMultiplier, varName);
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean rememberResult = sa.hasParam("RememberResult");
|
|
||||||
|
|
||||||
for (final Player flipper : playersToFlip) {
|
for (final Player flipper : playersToFlip) {
|
||||||
if (noCall) {
|
if (noCall) {
|
||||||
flipMultiplier = getFlipMultiplier(flipper);
|
int countHeads = flipCoins(flipper, sa, amount);
|
||||||
|
int countTails = Math.abs(countHeads - amount);
|
||||||
int countHeads = 0;
|
|
||||||
int countTails = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < amount; ++i) {
|
|
||||||
final boolean resultIsHeads = flipCoinNoCall(sa, flipper, flipMultiplier, varName);
|
|
||||||
|
|
||||||
if (resultIsHeads) {
|
|
||||||
countHeads++;
|
|
||||||
} else {
|
|
||||||
countTails++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rememberResult) {
|
|
||||||
host.addFlipResult(flipper, resultIsHeads ? "Heads" : "Tails");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (countHeads > 0) {
|
if (countHeads > 0) {
|
||||||
|
if (sa.hasParam("RememberResult")) {
|
||||||
|
host.addFlipResult(flipper, "Heads");
|
||||||
|
}
|
||||||
SpellAbility sub = sa.getAdditionalAbility("HeadsSubAbility");
|
SpellAbility sub = sa.getAdditionalAbility("HeadsSubAbility");
|
||||||
if (sub != null) {
|
if (sub != null) {
|
||||||
if (sa.hasParam("Amount")) {
|
if (sa.hasParam("Amount")) {
|
||||||
@@ -107,6 +78,9 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (countTails > 0) {
|
if (countTails > 0) {
|
||||||
|
if (sa.hasParam("RememberResult")) {
|
||||||
|
host.addFlipResult(flipper, "Tails");
|
||||||
|
}
|
||||||
SpellAbility sub = sa.getAdditionalAbility("TailsSubAbility");
|
SpellAbility sub = sa.getAdditionalAbility("TailsSubAbility");
|
||||||
if (sub != null) {
|
if (sub != null) {
|
||||||
if (sa.hasParam("Amount")) {
|
if (sa.hasParam("Amount")) {
|
||||||
@@ -115,46 +89,7 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
|||||||
AbilityUtils.resolve(sub);
|
AbilityUtils.resolve(sub);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (amount > 1) {
|
|
||||||
flipMultiplier = getFlipMultiplier(flipper);
|
|
||||||
|
|
||||||
int countWins = 0;
|
|
||||||
int countLosses = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < amount; ++i) {
|
|
||||||
final boolean win = flipCoinCall(caller.get(0), sa, flipMultiplier, varName);
|
|
||||||
|
|
||||||
if (win) {
|
|
||||||
countWins++;
|
|
||||||
} else {
|
|
||||||
countLosses++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (countWins > 0) {
|
|
||||||
SpellAbility sub = sa.getAdditionalAbility("WinSubAbility");
|
|
||||||
if (sub != null) {
|
|
||||||
sub.setSVar("Wins", "Number$" + countWins);
|
|
||||||
AbilityUtils.resolve(sub);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (countLosses > 0) {
|
|
||||||
SpellAbility sub = sa.getAdditionalAbility("LoseSubAbility");
|
|
||||||
if (sub != null) {
|
|
||||||
sub.setSVar("Losses", "Number$" + countLosses);
|
|
||||||
AbilityUtils.resolve(sub);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sa.hasParam("RememberNumber")) {
|
|
||||||
String toRemember = sa.getParam("RememberNumber");
|
|
||||||
if (toRemember.startsWith("Win")) {
|
|
||||||
host.addRemembered(countWins);
|
|
||||||
} else if (toRemember.startsWith("Loss")) {
|
|
||||||
host.addRemembered(countLosses);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (forEachPlayer) {
|
} else if (forEachPlayer) {
|
||||||
flipMultiplier = getFlipMultiplier(flipper);
|
|
||||||
|
|
||||||
int countWins = 0;
|
int countWins = 0;
|
||||||
int countLosses = 0;
|
int countLosses = 0;
|
||||||
PlayerCollection wonFor = new PlayerCollection();
|
PlayerCollection wonFor = new PlayerCollection();
|
||||||
@@ -162,9 +97,9 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
for (final Player p : AbilityUtils.getDefinedPlayers(host, sa.getParam("ForEachPlayer"), sa)) {
|
for (final Player p : AbilityUtils.getDefinedPlayers(host, sa.getParam("ForEachPlayer"), sa)) {
|
||||||
final String info = " (" + p.getName() +")";
|
final String info = " (" + p.getName() +")";
|
||||||
final boolean win = flipCoinCall(caller.get(0), sa, flipMultiplier, varName, info);
|
final int win = flipCoins(flipper, sa, 1, info);
|
||||||
|
|
||||||
if (win) {
|
if (win > 0) {
|
||||||
countWins++;
|
countWins++;
|
||||||
wonFor.add(p);
|
wonFor.add(p);
|
||||||
} else {
|
} else {
|
||||||
@@ -196,57 +131,59 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
|||||||
host.addRemembered(tempRemembered);
|
host.addRemembered(tempRemembered);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (victory) {
|
|
||||||
if (sa.hasParam("RememberWinner")) {
|
|
||||||
host.addRemembered(flipper);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.hasAdditionalAbility("WinSubAbility")) {
|
|
||||||
AbilityUtils.resolve(sa.getAdditionalAbility("WinSubAbility"));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (sa.hasParam("RememberLoser")) {
|
int countWins = flipCoins(flipper, sa, amount);
|
||||||
host.addRemembered(flipper);
|
int countLosses = Math.abs(countWins - amount);
|
||||||
|
if (countWins > 0) {
|
||||||
|
if (sa.hasParam("RememberWinner")) {
|
||||||
|
host.addRemembered(flipper);
|
||||||
|
}
|
||||||
|
SpellAbility sub = sa.getAdditionalAbility("WinSubAbility");
|
||||||
|
if (sub != null) {
|
||||||
|
sub.setSVar("Wins", "Number$" + countWins);
|
||||||
|
AbilityUtils.resolve(sub);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (countLosses > 0) {
|
||||||
if (sa.hasAdditionalAbility("LoseSubAbility")) {
|
if (sa.hasParam("RememberLoser")) {
|
||||||
AbilityUtils.resolve(sa.getAdditionalAbility("LoseSubAbility"));
|
host.addRemembered(flipper);
|
||||||
|
}
|
||||||
|
SpellAbility sub = sa.getAdditionalAbility("LoseSubAbility");
|
||||||
|
if (sub != null) {
|
||||||
|
sub.setSVar("Losses", "Number$" + countLosses);
|
||||||
|
AbilityUtils.resolve(sub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sa.hasParam("RememberNumber")) {
|
||||||
|
String toRemember = sa.getParam("RememberNumber");
|
||||||
|
if (toRemember.startsWith("Win")) {
|
||||||
|
host.addRemembered(countWins);
|
||||||
|
} else if (toRemember.startsWith("Loss")) {
|
||||||
|
host.addRemembered(countLosses);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static int flipCoins(final Player flipper, final SpellAbility sa, final int amount) {
|
||||||
* <p>
|
return flipCoins(flipper, sa, amount, "");
|
||||||
* flipCoinNoCall Flip a coin without any call.
|
}
|
||||||
* </p>
|
public static int flipCoins(final Player flipper, final SpellAbility sa, final int amount, final String info) {
|
||||||
*
|
int multiplier = getFlipMultiplier(flipper);
|
||||||
* @param sa the source card.
|
int result = 0;
|
||||||
* @param flipper the player flipping the coin.
|
boolean won = false;
|
||||||
* @param multiplier
|
|
||||||
* @return a boolean.
|
|
||||||
*/
|
|
||||||
public boolean flipCoinNoCall(final SpellAbility sa, final Player flipper, final int multiplier, final String varName) {
|
|
||||||
boolean result = false;
|
|
||||||
int numSuccesses = 0;
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
Set<Boolean> flipResults = new HashSet<>();
|
Boolean fixedResult = StaticAbilityFlipCoinMod.fixedResult(flipper);
|
||||||
for (int i = 0; i < multiplier; i++) {
|
for (int i = 0; i < amount; i++) {
|
||||||
flipResults.add(MyRandom.getRandom().nextBoolean());
|
won = flipCoin(flipper, sa, multiplier, fixedResult, info);
|
||||||
|
if (won) {
|
||||||
|
result++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
flipper.getGame().fireEvent(new GameEventFlipCoin());
|
// until is sequential
|
||||||
result = flipResults.size() == 1 ? flipResults.iterator().next() : flipper.getController().chooseFlipResult(sa, flipper, BOTH_CHOICES, false);
|
|
||||||
if (result) {
|
|
||||||
numSuccesses++;
|
|
||||||
}
|
|
||||||
flipper.getGame().getAction().notifyOfValue(sa, flipper, result ? Localizer.getInstance().getMessage("lblHeads") : Localizer.getInstance().getMessage("lblTails"), null);
|
|
||||||
} while (sa.hasParam("FlipUntilYouLose") && result != false);
|
|
||||||
|
|
||||||
if (sa.hasParam("FlipUntilYouLose") && sa.hasAdditionalAbility("LoseSubAbility")) {
|
|
||||||
sa.getAdditionalAbility("LoseSubAbility").setSVar(varName, "Number$" + numSuccesses);
|
|
||||||
}
|
}
|
||||||
|
while (sa.hasParam("FlipUntilYouLose") && won);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,50 +192,50 @@ public class FlipCoinEffect extends SpellAbilityEffect {
|
|||||||
* flipCoinCall.
|
* flipCoinCall.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param caller
|
* @param flipper
|
||||||
* @param sa
|
* @param sa
|
||||||
* @param multiplier
|
* @param multiplier
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean flipCoinCall(final Player caller, final SpellAbility sa, final int multiplier) {
|
private static boolean flipCoin(final Player flipper, final SpellAbility sa, int multiplier, final Boolean fixedResult, final String info) {
|
||||||
String varName = sa.getParamOrDefault("SaveNumFlipsToSVar", "X");
|
Set<Boolean> flipResults = new HashSet<>();
|
||||||
return flipCoinCall(caller, sa, multiplier, varName, "");
|
boolean noCall = sa.hasParam("NoCall");
|
||||||
}
|
boolean choice = true;
|
||||||
public static boolean flipCoinCall(final Player caller, final SpellAbility sa, final int multiplier, final String varName) {
|
if (fixedResult != null) {
|
||||||
return flipCoinCall(caller, sa, multiplier, varName, "");
|
flipResults.add(fixedResult);
|
||||||
}
|
} else {
|
||||||
public static boolean flipCoinCall(final Player caller, final SpellAbility sa, final int multiplier, final String varName, final String info) {
|
// no reason to ask if result is fixed anyway
|
||||||
boolean wonFlip = false;
|
if (!noCall) {
|
||||||
int numSuccesses = 0;
|
choice = flipper.getController().chooseBinary(sa, sa.getHostCard().getName() + " - " + Localizer.getInstance().getMessage("lblCallCoinFlip") + info, PlayerController.BinaryChoiceType.HeadsOrTails);
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
|
||||||
Set<Boolean> flipResults = new HashSet<>();
|
|
||||||
final boolean choice = caller.getController().chooseBinary(sa, sa.getHostCard().getName() + " - " + Localizer.getInstance().getMessage("lblCallCoinFlip") + info, PlayerController.BinaryChoiceType.HeadsOrTails);
|
|
||||||
for (int i = 0; i < multiplier; i++) {
|
for (int i = 0; i < multiplier; i++) {
|
||||||
flipResults.add(MyRandom.getRandom().nextBoolean());
|
flipResults.add(MyRandom.getRandom().nextBoolean());
|
||||||
}
|
}
|
||||||
// Play the Flip A Coin sound
|
|
||||||
caller.getGame().fireEvent(new GameEventFlipCoin());
|
|
||||||
boolean result = flipResults.size() == 1 ? flipResults.iterator().next() : caller.getController().chooseFlipResult(sa, caller, BOTH_CHOICES, true);
|
|
||||||
wonFlip = result == choice;
|
|
||||||
|
|
||||||
if (wonFlip) {
|
|
||||||
numSuccesses++;
|
|
||||||
}
|
|
||||||
|
|
||||||
caller.getGame().getAction().notifyOfValue(sa, caller, wonFlip ? Localizer.getInstance().getMessage("lblWin") : Localizer.getInstance().getMessage("lblLose"), null);
|
|
||||||
|
|
||||||
// Run triggers
|
|
||||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(caller);
|
|
||||||
runParams.put(AbilityKey.Result, wonFlip);
|
|
||||||
caller.getGame().getTriggerHandler().runTrigger(TriggerType.FlippedCoin, runParams, false);
|
|
||||||
} while (sa.hasParam("FlipUntilYouLose") && wonFlip);
|
|
||||||
|
|
||||||
if (sa.hasParam("FlipUntilYouLose") && sa.hasAdditionalAbility("LoseSubAbility")) {
|
|
||||||
sa.getAdditionalAbility("LoseSubAbility").setSVar(varName, "Number$" + numSuccesses);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return wonFlip;
|
boolean result = flipResults.size() == 1 ? flipResults.iterator().next() : flipper.getController().chooseFlipResult(sa, flipper, BOTH_CHOICES, true);
|
||||||
|
boolean wonOrHeads = result == choice;
|
||||||
|
|
||||||
|
String outcome;
|
||||||
|
if (noCall) {
|
||||||
|
outcome = wonOrHeads ? Localizer.getInstance().getMessage("lblHeads") : Localizer.getInstance().getMessage("lblTails");
|
||||||
|
} else {
|
||||||
|
outcome = wonOrHeads ? Localizer.getInstance().getMessage("lblWin") : Localizer.getInstance().getMessage("lblLose");
|
||||||
|
}
|
||||||
|
// Play the Flip A Coin sound
|
||||||
|
flipper.getGame().fireEvent(new GameEventFlipCoin());
|
||||||
|
flipper.getGame().getAction().notifyOfValue(sa, flipper, outcome, null);
|
||||||
|
|
||||||
|
flipper.flip();
|
||||||
|
|
||||||
|
if (!noCall || fixedResult != null) {
|
||||||
|
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(flipper);
|
||||||
|
runParams.put(AbilityKey.Result, wonOrHeads);
|
||||||
|
flipper.getGame().getTriggerHandler().runTrigger(TriggerType.FlippedCoin, runParams, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return wonOrHeads;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getFlipMultiplier(final Player flipper) {
|
public static int getFlipMultiplier(final Player flipper) {
|
||||||
|
|||||||
@@ -60,18 +60,21 @@ public class ManaEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
if (abMana.isComboMana()) {
|
if (abMana.isComboMana()) {
|
||||||
int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa) : 1;
|
int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa) : 1;
|
||||||
if(amount <= 0)
|
if (amount <= 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
String express = abMana.getExpressChoice();
|
String combo = abMana.getComboColors(sa);
|
||||||
String[] colorsProduced = abMana.getComboColors(sa).split(" ");
|
if (combo.isBlank()) {
|
||||||
|
return;
|
||||||
final StringBuilder choiceString = new StringBuilder();
|
}
|
||||||
final StringBuilder choiceSymbols = new StringBuilder();
|
String[] colorsProduced = combo.split(" ");
|
||||||
ColorSet colorOptions = ColorSet.fromNames(colorsProduced);
|
ColorSet colorOptions = ColorSet.fromNames(colorsProduced);
|
||||||
|
String express = abMana.getExpressChoice();
|
||||||
String[] colorsNeeded = express.isEmpty() ? null : express.split(" ");
|
String[] colorsNeeded = express.isEmpty() ? null : express.split(" ");
|
||||||
boolean differentChoice = abMana.getOrigProduced().contains("Different");
|
boolean differentChoice = abMana.getOrigProduced().contains("Different");
|
||||||
ColorSet fullOptions = colorOptions;
|
ColorSet fullOptions = colorOptions;
|
||||||
|
final StringBuilder choiceString = new StringBuilder();
|
||||||
|
final StringBuilder choiceSymbols = new StringBuilder();
|
||||||
// Use specifyManaCombo if possible
|
// Use specifyManaCombo if possible
|
||||||
if (colorsNeeded == null && amount > 1 && !sa.hasParam("TwoEach")) {
|
if (colorsNeeded == null && amount > 1 && !sa.hasParam("TwoEach")) {
|
||||||
Map<Byte, Integer> choices = chooser.getController().specifyManaCombo(sa, colorOptions, amount, differentChoice);
|
Map<Byte, Integer> choices = chooser.getController().specifyManaCombo(sa, colorOptions, amount, differentChoice);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package forge.game.ability.effects;
|
package forge.game.ability.effects;
|
||||||
|
|
||||||
import forge.game.card.Card;
|
import forge.game.card.CardState;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.util.CardTranslation;
|
import forge.util.CardTranslation;
|
||||||
import forge.util.Localizer;
|
import forge.util.Localizer;
|
||||||
@@ -13,10 +13,10 @@ public class PermanentCreatureEffect extends PermanentEffect {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getStackDescription(final SpellAbility sa) {
|
public String getStackDescription(final SpellAbility sa) {
|
||||||
final Card sourceCard = sa.getHostCard();
|
final CardState source = sa.getCardState();
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
sb.append(CardTranslation.getTranslatedName(sourceCard.getName())).append(" - ").append(Localizer.getInstance().getMessage("lblCreature")).append(" ").append(sourceCard.getNetPower());
|
sb.append(CardTranslation.getTranslatedName(source.getName())).append(" - ").append(Localizer.getInstance().getMessage("lblCreature")).append(" ").append(source.getBasePowerString());
|
||||||
sb.append(" / ").append(sourceCard.getNetToughness());
|
sb.append(" / ").append(source.getBaseToughnessString());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user