mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-15 18:28:00 +00:00
Compare commits
775 Commits
switchedPT
...
betterImag
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5938e5609 | ||
|
|
15dd063078 | ||
|
|
210fd6dbe3 | ||
|
|
313d1b849e | ||
|
|
0b4ab12f47 | ||
|
|
8e9fb8570e | ||
|
|
5a183a6042 | ||
|
|
67e6a7aa1a | ||
|
|
b76c67f309 | ||
|
|
99c6b6d815 | ||
|
|
cadc39699d | ||
|
|
8c06aab7b3 | ||
|
|
25c59cd5dd | ||
|
|
9825239e43 | ||
|
|
fb69f245da | ||
|
|
7ef8dddc2a | ||
|
|
2abcae84b1 | ||
|
|
b4beb6c182 | ||
|
|
786d14663b | ||
|
|
f8269e69c4 | ||
|
|
29274d0acf | ||
|
|
d6b925f171 | ||
|
|
f1f18d1823 | ||
|
|
03ff2147db | ||
|
|
c66a72806d | ||
|
|
e89f2cbac0 | ||
|
|
d831530c50 | ||
|
|
9fe18c2af8 | ||
|
|
fe4ff7ac0f | ||
|
|
a8ca3d8188 | ||
|
|
3b9867c537 | ||
|
|
01d22c26f4 | ||
|
|
e8f7fe5a95 | ||
|
|
bd0b8fbc65 | ||
|
|
05e20b4e92 | ||
|
|
716d58ad4b | ||
|
|
ca12ad529a | ||
|
|
6eea82706d | ||
|
|
d69d005ce0 | ||
|
|
b98b322dfe | ||
|
|
e73e72d150 | ||
|
|
8cabc244f7 | ||
|
|
4f04e5cc13 | ||
|
|
bcf9d2585d | ||
|
|
e03b07f940 | ||
|
|
a45c6aa37e | ||
|
|
dd672945f2 | ||
|
|
e634be4273 | ||
|
|
9acf5bee41 | ||
|
|
1e3297ab64 | ||
|
|
58df290c8e | ||
|
|
e2075886b1 | ||
|
|
ad53abc75a | ||
|
|
a599c318dd | ||
|
|
14e2a0c5e2 | ||
|
|
2ae3efc12e | ||
|
|
fb18134c12 | ||
|
|
2215a101a3 | ||
|
|
4df2fe7ac5 | ||
|
|
c24369d1ec | ||
|
|
41eb86029a | ||
|
|
357026ce66 | ||
|
|
9b558cb069 | ||
|
|
303020ca75 | ||
|
|
b900abcc71 | ||
|
|
c4ba6df6e9 | ||
|
|
cc4a507799 | ||
|
|
d8de8ec696 | ||
|
|
e6563814e8 | ||
|
|
016d51669f | ||
|
|
f19828b2ec | ||
|
|
36fc87c2a1 | ||
|
|
4322bddabf | ||
|
|
c128a9d4ba | ||
|
|
2feeffc95c | ||
|
|
53de238a7e | ||
|
|
c207e74369 | ||
|
|
04ca02a77a | ||
|
|
6ff89c71b6 | ||
|
|
d7c6a8e53e | ||
|
|
150741a443 | ||
|
|
460f322c44 | ||
|
|
ef5d35fe38 | ||
|
|
16044556b5 | ||
|
|
81d5079468 | ||
|
|
edc4b22cbc | ||
|
|
c01dfd4740 | ||
|
|
53a4a46d1f | ||
|
|
d61e7e7102 | ||
|
|
17f4df9293 | ||
|
|
24071582bb | ||
|
|
5754466a0d | ||
|
|
a18eda92b7 | ||
|
|
11d0a8f2fe | ||
|
|
2b8b386882 | ||
|
|
45a5027451 | ||
|
|
2cb72f55f3 | ||
|
|
fa27fbab46 | ||
|
|
87821fe287 | ||
|
|
cee4bcd867 | ||
|
|
b9cc4c18a8 | ||
|
|
4577e61940 | ||
|
|
9ac6147a98 | ||
|
|
74d4ea8a78 | ||
|
|
e2f4c7f872 | ||
|
|
07db8e5a94 | ||
|
|
ed7fb03b9c | ||
|
|
f0ebc3e6a0 | ||
|
|
f2570b4cef | ||
|
|
97e1939021 | ||
|
|
bebb894f3e | ||
|
|
288c533c91 | ||
|
|
6891f9a5d5 | ||
|
|
37a62eed6b | ||
|
|
3ee6b0e58d | ||
|
|
7e6f772345 | ||
|
|
ef28173b65 | ||
|
|
a26614c6b6 | ||
|
|
ee00293e48 | ||
|
|
ce977387e6 | ||
|
|
8cc45dbbd8 | ||
|
|
851a4c104c | ||
| 45ddf3fded | |||
|
|
cb538f4026 | ||
|
|
6bd6db3c16 | ||
|
|
6af3940ffb | ||
|
|
66c864c8d7 | ||
|
|
15354dd8e8 | ||
|
|
215070fe3f | ||
|
|
ed73883e80 | ||
|
|
df85ebc0aa | ||
|
|
885a6af943 | ||
|
|
94820d4782 | ||
|
|
7f044e5ac3 | ||
|
|
1284f23620 | ||
|
|
bdcc8acdc4 | ||
|
|
73a9dfcf43 | ||
|
|
7a277ce283 | ||
|
|
f8b3f9dd30 | ||
|
|
840f3ea96c | ||
|
|
51929434be | ||
|
|
3ec480afa4 | ||
|
|
3d19e9e444 | ||
|
|
6d987791f7 | ||
|
|
7b7da00c22 | ||
|
|
34791bd892 | ||
|
|
e6bcd1be72 | ||
|
|
4cac354983 | ||
|
|
042f7f77a9 | ||
|
|
5fbb1dd0cd | ||
|
|
bb14dfc00e | ||
|
|
f83cc4ccfa | ||
|
|
d0d6835a5d | ||
|
|
104bc8fc55 | ||
|
|
057dd867a8 | ||
|
|
9eed8f5095 | ||
|
|
1f62be9773 | ||
|
|
7a46f92059 | ||
|
|
cfa79b9676 | ||
|
|
c0a63fa15b | ||
|
|
c61537ae16 | ||
|
|
3871095b92 | ||
|
|
dee846da49 | ||
|
|
2477553d13 | ||
|
|
c9df7d7f8e | ||
|
|
53cb093f9e | ||
|
|
28e86970dc | ||
|
|
f847fc1669 | ||
|
|
f3df55177a | ||
|
|
be6f345127 | ||
|
|
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 |
139
.github/workflows/maven-publish.yml
vendored
139
.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,132 @@ 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 -DskipTests -Dskip.flatten=true -e -T 1C release:clean release:prepare release:perform
|
||||||
|
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: 🔧 Install XML tools
|
||||||
|
run: sudo apt-get install -y libxml2-utils
|
||||||
|
|
||||||
|
- name: 🔼 Bump versionCode in root POM
|
||||||
|
id: bump_version
|
||||||
|
run: |
|
||||||
|
cd /home/runner/work/forge/forge/
|
||||||
|
|
||||||
|
current_version=$(xmllint --xpath "//*[local-name()='versionCode']/text()" pom.xml)
|
||||||
|
echo "Current versionCode: $current_version"
|
||||||
|
|
||||||
|
IFS='.' read -r major minor patch <<< "${current_version}"
|
||||||
|
new_patch=$(printf "%02d" $((10#$patch + 1)))
|
||||||
|
new_version="${major}.${minor}.${new_patch}"
|
||||||
|
|
||||||
|
sed -i -E "s|<versionCode>.*</versionCode>|<versionCode>${new_version}</versionCode>|" pom.xml
|
||||||
|
|
||||||
|
echo "version_code=${new_version}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: ♻️ Restore {revision} in child POMs
|
||||||
|
run: |
|
||||||
|
find . -name pom.xml ! -path "./pom.xml" | while read -r pom; do
|
||||||
|
sed -i -E 's|<version>2\.0+\.[0-9]+(-SNAPSHOT)?</version>|<version>${revision}</version>|' "$pom"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: 💾 Commit restored {revision}
|
||||||
|
run: |
|
||||||
|
# Add only pom.xml files
|
||||||
|
find . -name pom.xml -exec git add {} \;
|
||||||
|
|
||||||
|
# Commit if there are changes
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "No pom.xml changes to commit."
|
||||||
|
else
|
||||||
|
git commit -m "Restore POM files for preparation of next release" || echo "No changes to commit"
|
||||||
|
git push
|
||||||
|
fi
|
||||||
|
|
||||||
|
- 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 }}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Dev instructions here: [Getting Started](https://github.com/Card-Forge/forge/wik
|
|||||||
|
|
||||||
## Requirements / Tools
|
## Requirements / Tools
|
||||||
|
|
||||||
- you favourite Java IDE (IntelliJ, Eclipse, VSCodium, Emacs, Vi...)
|
- your favourite Java IDE (IntelliJ, Eclipse, VSCodium, Emacs, Vi...)
|
||||||
- Java JDK 17 or later
|
- Java JDK 17 or later
|
||||||
- Git
|
- Git
|
||||||
- Git client (optional)
|
- Git client (optional)
|
||||||
@@ -28,7 +28,6 @@ Dev instructions here: [Getting Started](https://github.com/Card-Forge/forge/wik
|
|||||||
|
|
||||||
IntelliJ is the recommended IDE for Forge development. Quick start guide for [setting up the Forge project within IntelliJ](https://github.com/Card-Forge/forge/wiki/IntelliJ-setup).
|
IntelliJ is the recommended IDE for Forge development. Quick start guide for [setting up the Forge project within IntelliJ](https://github.com/Card-Forge/forge/wiki/IntelliJ-setup).
|
||||||
|
|
||||||
|
|
||||||
## Eclipse
|
## Eclipse
|
||||||
|
|
||||||
Eclipse includes Maven integration so a separate install is not necessary. For other IDEs, your mileage may vary.
|
Eclipse includes Maven integration so a separate install is not necessary. For other IDEs, your mileage may vary.
|
||||||
@@ -124,10 +123,11 @@ TBD
|
|||||||
|
|
||||||
SNAPSHOT builds can be built via the Maven integration in Eclipse.
|
SNAPSHOT builds can be built via the Maven integration in Eclipse.
|
||||||
|
|
||||||
1) Create a Maven build for the forge top-level project. Right-click on the forge project. Run as.. > Maven build...
|
1. Create a Maven build for the forge top-level project. Right-click on the forge project. Run as.. > Maven build...
|
||||||
|
|
||||||
- On the Main tab, set Goals: clean install, set Profiles: windows-linux
|
- On the Main tab, set Goals: clean install, set Profiles: windows-linux
|
||||||
|
|
||||||
2) Run forge Maven build. If everything built, you should see "BUILD SUCCESS" in the Console View.
|
2. Run forge Maven build. If everything built, you should see "BUILD SUCCESS" in the Console View.
|
||||||
|
|
||||||
The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desktop-[version]-SNAPSHOT
|
The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desktop-[version]-SNAPSHOT
|
||||||
|
|
||||||
@@ -158,13 +158,19 @@ The platform-specific projects are:
|
|||||||
|
|
||||||
#### forge-ai
|
#### forge-ai
|
||||||
|
|
||||||
|
The forge-ai project contains the computer opponent logic for gameplay. It includes decision-making algorithms for specific abilities, cards and turn phases.
|
||||||
|
|
||||||
#### forge-core
|
#### forge-core
|
||||||
|
|
||||||
|
The forge-core project contains the core game engine, card mechanics, rules engine, and fundamental game logic. It includes the implementation of Magic: The Gathering rules, card interactions, and the game state management system.
|
||||||
|
|
||||||
#### forge-game
|
#### forge-game
|
||||||
|
|
||||||
|
The forge-game project handles the game session management, player interactions, and game flow control. It includes implementations for multiplayer support, game modes, matchmaking, and game state persistence. This module bridges the core game engine with the user interface and networking components.
|
||||||
|
|
||||||
#### forge-gui
|
#### forge-gui
|
||||||
|
|
||||||
The forge-gui project includes the scripting resource definitions in the res/ path.
|
The forge-gui project contains the user interface components and rendering logic for the game. It includes the main game window, card displays, player interactions, and the scripting resource definitions in the res/ path.
|
||||||
|
|
||||||
#### forge-gui-android
|
#### forge-gui-android
|
||||||
|
|
||||||
|
|||||||
@@ -26,13 +26,14 @@ 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.
|
- _(Note: **Android 11** is the minimum requirements with at least **6GB RAM** to run smoothly. You need to enable **"Install unknown apps"** for Forge to initialize and update itself)_
|
||||||
|
- 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.completeOnTimeout(null, game.getAITimeout(), TimeUnit.SECONDS).get();
|
|
||||||
else
|
|
||||||
return future.get(game.getAITimeout(), TimeUnit.SECONDS);
|
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,7 +642,8 @@ 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)
|
||||||
|
if (ai.getControllingPlayer() == null) {
|
||||||
manapool.restoreColorReplacements();
|
manapool.restoreColorReplacements();
|
||||||
CardPlayOption mayPlay = sa.getMayPlayOption();
|
CardPlayOption mayPlay = sa.getMayPlayOption();
|
||||||
if (!effect) {
|
if (!effect) {
|
||||||
@@ -656,10 +657,13 @@ public class ComputerUtilMana {
|
|||||||
AbilityUtils.applyManaColorConversion(manapool, sa.getParam("ManaConversion"));
|
AbilityUtils.applyManaColorConversion(manapool, sa.getParam("ManaConversion"));
|
||||||
}
|
}
|
||||||
StaticAbilityManaConvert.manaConvert(manapool, ai, sa.getHostCard(), effect && !sa.isCastFromPlayEffect() ? null : sa);
|
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)) {
|
||||||
|
|||||||
@@ -359,18 +359,28 @@ public class SpecialCardAi {
|
|||||||
private static final int demonSacThreshold = Integer.MAX_VALUE; // if we're in dire conditions, sac everything from worst to best hoping to find an answer
|
private static final int demonSacThreshold = Integer.MAX_VALUE; // if we're in dire conditions, sac everything from worst to best hoping to find an answer
|
||||||
|
|
||||||
public static boolean considerSacrificingCreature(final Player ai, final SpellAbility sa) {
|
public static boolean considerSacrificingCreature(final Player ai, final SpellAbility sa) {
|
||||||
|
Card c = sa.getHostCard();
|
||||||
|
|
||||||
|
// Only check for sacrifice if it's the owner's turn, and it can attack.
|
||||||
|
// TODO: Maybe check if sacrificing a creature allows AI to kill the opponent with the rest on their turn?
|
||||||
|
if (!CombatUtil.canAttack(c) ||
|
||||||
|
!ai.getGame().getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
CardCollection flyingCreatures = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
|
CardCollection flyingCreatures = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
|
||||||
CardPredicates.UNTAPPED.and(
|
CardPredicates.UNTAPPED.and(
|
||||||
CardPredicates.hasKeyword(Keyword.FLYING).or(CardPredicates.hasKeyword(Keyword.REACH))));
|
CardPredicates.hasKeyword(Keyword.FLYING).or(CardPredicates.hasKeyword(Keyword.REACH))));
|
||||||
boolean hasUsefulBlocker = false;
|
boolean hasUsefulBlocker = false;
|
||||||
|
|
||||||
for (Card c : flyingCreatures) {
|
for (Card fc : flyingCreatures) {
|
||||||
if (!ComputerUtilCard.isUselessCreature(ai, c)) {
|
if (!ComputerUtilCard.isUselessCreature(ai, fc)) {
|
||||||
hasUsefulBlocker = true;
|
hasUsefulBlocker = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ai.getLife() <= sa.getHostCard().getNetPower() && !hasUsefulBlocker;
|
return ai.getLife() <= c.getNetPower() && !hasUsefulBlocker;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getSacThreshold() {
|
public static int getSacThreshold() {
|
||||||
@@ -1469,6 +1479,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;
|
||||||
@@ -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,21 +420,10 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// already affected
|
||||||
if (!c.isEnchanted()) {
|
if (!c.canUntap(c.getController(), true)) {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -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) {
|
||||||
@@ -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(" & ")));
|
||||||
}
|
}
|
||||||
@@ -1158,12 +1238,17 @@ 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)) {
|
||||||
|
String o = ki.getOriginal();
|
||||||
|
String m[] = o.split(":");
|
||||||
|
String v = m[1];
|
||||||
|
if (!v.startsWith("Creature")) {
|
||||||
canOnlyTargetCreatures = false;
|
canOnlyTargetCreatures = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (canOnlyTargetCreatures && (attachSource.isAura() || attachSource.isEquipment())) {
|
if (canOnlyTargetCreatures && (attachSource.isAura() || attachSource.isEquipment())) {
|
||||||
prefList = CardLists.filter(prefList, c -> c.getTimesCrewedThisTurn() == 0 || (attachSource.isEquipment() && attachSource.getGame().getPhaseHandler().is(PhaseType.MAIN1, ai)));
|
prefList = CardLists.filter(prefList, c -> c.getTimesCrewedThisTurn() == 0 || (attachSource.isEquipment() && attachSource.getGame().getPhaseHandler().is(PhaseType.MAIN1, ai)));
|
||||||
}
|
}
|
||||||
@@ -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,8 +1285,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
list.remove(choice);
|
list.remove(choice);
|
||||||
|
if (sa.canTarget(choice)) {
|
||||||
sa.getTargets().add(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.
|
||||||
// TODO: ideally the AI should consider at this point which targets exactly to pick (e.g. one card in the first player's graveyard
|
// TODO: ideally the AI should consider at this point which targets exactly to pick (e.g. one card in the first player's graveyard
|
||||||
@@ -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,8 +287,10 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
list.remove(choice);
|
list.remove(choice);
|
||||||
|
if (sa.canTarget(choice)) {
|
||||||
sa.getTargets().add(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);
|
||||||
if ("WillSkipTurn".equals(logic) && (source.getController().equals(ai)
|
if ("WillSkipTurn".equals(logic) && (source.getController().equals(ai)
|
||||||
@@ -361,7 +366,10 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Card c = ComputerUtilCard.getBestAI(preferred);
|
Card c = ComputerUtilCard.getBestAI(preferred);
|
||||||
|
|
||||||
|
if (sa.canTarget(c)) {
|
||||||
sa.getTargets().add(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);
|
||||||
}
|
}
|
||||||
|
if (sa.canTarget(c)) {
|
||||||
sa.getTargets().add(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;
|
||||||
}
|
}
|
||||||
@@ -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,10 +23,14 @@ 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_W = "$wspec";
|
||||||
@@ -35,6 +39,9 @@ public final class ImageKeys {
|
|||||||
public static final String SPECFACE_R = "$rspec";
|
public static final String SPECFACE_R = "$rspec";
|
||||||
public static final String SPECFACE_G = "$gspec";
|
public static final String SPECFACE_G = "$gspec";
|
||||||
|
|
||||||
|
private static final String URL_SCRYFALL = "https://api.scryfall.com";
|
||||||
|
public static final String URL_PIC_SCRYFALL_DOWNLOAD = URL_SCRYFALL + "/cards/";
|
||||||
|
|
||||||
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;
|
||||||
public static String ADVENTURE_CARD_PICS_DIR;
|
public static String ADVENTURE_CARD_PICS_DIR;
|
||||||
@@ -98,8 +105,18 @@ public final class ImageKeys {
|
|||||||
|
|
||||||
final String dir;
|
final String dir;
|
||||||
final String filename;
|
final String filename;
|
||||||
|
String[] tempdata = null;
|
||||||
if (key.startsWith(ImageKeys.TOKEN_PREFIX)) {
|
if (key.startsWith(ImageKeys.TOKEN_PREFIX)) {
|
||||||
filename = key.substring(ImageKeys.TOKEN_PREFIX.length());
|
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 +157,30 @@ public final class ImageKeys {
|
|||||||
cachedCards.put(filename, file);
|
cachedCards.put(filename, file);
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
if (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 +262,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)
|
if (artIndex > 0)
|
||||||
artIndexFilter = (c.getArtIndex() == artIndex);
|
filter = filter.and((c) -> artIndex == c.getArtIndex());
|
||||||
if ((collectorNumber != null) && (collectorNumber.length() > 0)
|
if (collectorNumber != null && !collectorNumber.isEmpty() && !collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER))
|
||||||
&& !(collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER)))
|
filter = filter.and((c) -> collectorNumber.equals(c.getCollectorNumber()));
|
||||||
collectorNumberFilter = (c.getCollectorNumber().equals(collectorNumber));
|
|
||||||
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
|
||||||
@@ -821,11 +852,6 @@ 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,43 +166,6 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CardInSet implements Comparable<CardInSet> {
|
|
||||||
public final CardRarity rarity;
|
|
||||||
public final String collectorNumber;
|
|
||||||
public final String name;
|
|
||||||
public final String artistName;
|
|
||||||
public final String functionalVariantName;
|
|
||||||
|
|
||||||
public CardInSet(final String name, final String collectorNumber, final CardRarity rarity, final String artistName, final String functionalVariantName) {
|
|
||||||
this.name = name;
|
|
||||||
this.collectorNumber = collectorNumber;
|
|
||||||
this.rarity = rarity;
|
|
||||||
this.artistName = artistName;
|
|
||||||
this.functionalVariantName = functionalVariantName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
if (collectorNumber != null) {
|
|
||||||
sb.append(collectorNumber);
|
|
||||||
sb.append(' ');
|
|
||||||
}
|
|
||||||
if (rarity != CardRarity.Unknown) {
|
|
||||||
sb.append(rarity);
|
|
||||||
sb.append(' ');
|
|
||||||
}
|
|
||||||
sb.append(name);
|
|
||||||
if (artistName != null) {
|
|
||||||
sb.append(" @");
|
|
||||||
sb.append(artistName);
|
|
||||||
}
|
|
||||||
if (functionalVariantName != null) {
|
|
||||||
sb.append(" $");
|
|
||||||
sb.append(functionalVariantName);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Map<String, String> sortableCollNumberLookup = new HashMap<>();
|
private static final Map<String, String> sortableCollNumberLookup = new HashMap<>();
|
||||||
/**
|
/**
|
||||||
* This method implements the main strategy to allow for natural ordering of collectorNumber
|
* This method implements the main strategy to allow for natural ordering of collectorNumber
|
||||||
@@ -244,8 +208,32 @@ public final class CardEdition implements Comparable<CardEdition> {
|
|||||||
return sortableCollNr;
|
return sortableCollNr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record EditionEntry(String name, String collectorNumber, CardRarity rarity, String artistName, String functionalVariantName) implements Comparable<EditionEntry> {
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (collectorNumber != null) {
|
||||||
|
sb.append(collectorNumber);
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
if (rarity != CardRarity.Unknown && rarity != CardRarity.Token) {
|
||||||
|
sb.append(rarity);
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
sb.append(name);
|
||||||
|
if (artistName != null) {
|
||||||
|
sb.append(" @");
|
||||||
|
sb.append(artistName);
|
||||||
|
}
|
||||||
|
if (functionalVariantName != null) {
|
||||||
|
sb.append(" $");
|
||||||
|
sb.append(functionalVariantName);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
@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,33 @@ 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 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 +541,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 +595,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 +609,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 +653,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 +667,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 +838,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 +870,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 +1001,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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,7 +285,10 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
CardType type = mainPart.getType();
|
CardType type = mainPart.getType();
|
||||||
if (type.isLegendary() && canBeCreature()) {
|
if (!type.isLegendary()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (canBeCreature() || type.isVehicle() || type.isSpacecraft()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -396,6 +399,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()) {
|
||||||
@@ -522,6 +529,9 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
|||||||
return hasSubtype("Contraption");
|
return hasSubtype("Contraption");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isVehicle() { return hasSubtype("Vehicle"); }
|
||||||
|
public boolean isSpacecraft() { return hasSubtype("Spacecraft"); }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSaga() {
|
public boolean isSaga() {
|
||||||
return hasSubtype("Saga");
|
return hasSubtype("Saga");
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|
||||||
public interface ICardDatabase extends Iterable<PaperCard> {
|
|
||||||
/**
|
/**
|
||||||
* Magic Cards Database.
|
* Magic Cards Database.
|
||||||
* --------------------
|
* --------------------
|
||||||
* This interface defines the general API for Database Access and Cards' Lookup.
|
* This interface defines the general API for Database Access and Cards' Lookup.
|
||||||
*
|
* <p>
|
||||||
* Methods for single Card's lookup currently support three alternative strategies:
|
* Methods for single Card's lookup currently support three alternative strategies:
|
||||||
* 1. [getCard]: Card search based on a single card's attributes
|
* 1. [getCard]: Card search based on a single card's attributes
|
||||||
* (i.e. name, edition, art, collectorNumber)
|
* (i.e. name, edition, art, collectorNumber)
|
||||||
*
|
* <p>
|
||||||
* 2. [getCardFromSet]: Card Lookup from a single Expansion set.
|
* 2. [getCardFromSet]: Card Lookup from a single Expansion set.
|
||||||
* Particularly useful in Deck Editors when a specific Set is specified.
|
* 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
|
* 3. [getCardFromEditions]: Card search considering a predefined `SetPreference` policy and/or a specified Date
|
||||||
* when no expansion is specified for a card.
|
* when no expansion is specified for a card.
|
||||||
* This method is particularly useful for Re-prints whenever no specific
|
* 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
|
* 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
|
* on which card to pick. This methods allows to adopt a SetPreference selection
|
||||||
* policy to make this decision.
|
* policy to make this decision.
|
||||||
*
|
* <p>
|
||||||
* The API also includes methods to fetch Collection of Card (i.e. PaperCard instances):
|
* The API also includes methods to fetch Collection of Card (i.e. PaperCard instances):
|
||||||
* - all cards (no filter)
|
* - all cards (no filter)
|
||||||
* - all unique cards (by name)
|
* - all unique cards (by name)
|
||||||
* - all prints of a single card
|
* - all prints of a single card
|
||||||
* - all cards from a single Expansion Set
|
* - all cards from a single Expansion Set
|
||||||
* - all cards compliant with a filter condition (i.e. Predicate)
|
* - all cards compliant with a filter condition (i.e. Predicate)
|
||||||
*
|
* <p>
|
||||||
* Finally, various utility methods are supported:
|
* Finally, various utility methods are supported:
|
||||||
* - Get the foil version of a Card (if Any)
|
* - Get the foil version of a Card (if Any)
|
||||||
* - Get the Order Number of a Card in an Expansion Set
|
* - 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)
|
* - 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> {
|
||||||
/* 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()));
|
||||||
}
|
}
|
||||||
|
|||||||
13
forge-core/src/main/java/forge/item/ArtStyle.java
Normal file
13
forge-core/src/main/java/forge/item/ArtStyle.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package forge.item;
|
||||||
|
|
||||||
|
public enum ArtStyle {
|
||||||
|
Normal("fullborder", "normal"),
|
||||||
|
Crop("artcrop", "art_crop");
|
||||||
|
|
||||||
|
public final String filename;
|
||||||
|
public final String scryfall;
|
||||||
|
ArtStyle(String filename, String scryfall) {
|
||||||
|
this.filename = filename;
|
||||||
|
this.scryfall = scryfall;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
100
forge-core/src/main/java/forge/item/ImageKey.java
Normal file
100
forge-core/src/main/java/forge/item/ImageKey.java
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package forge.item;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ImageKeys;
|
||||||
|
import forge.StaticData;
|
||||||
|
import forge.card.CardEdition;
|
||||||
|
import forge.card.CardStateName;
|
||||||
|
|
||||||
|
public record ImageKey(String setCode, String name, String collectorNumber, String artistName, CardStateName state, ImageType type, boolean custom) implements Serializable
|
||||||
|
{
|
||||||
|
|
||||||
|
public List<String> getFilename(ArtStyle art) {
|
||||||
|
List<String> result = Lists.newArrayList();
|
||||||
|
String cn = collectorNumber;
|
||||||
|
if (type == ImageType.Token) {
|
||||||
|
if (ImageKeys.HIDDEN_CARD.equals(name)) {
|
||||||
|
// hidden only exist as png
|
||||||
|
result.add("hidden.png");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// TODO Token doesn't use fullborder or artcrop ArtStyle yet
|
||||||
|
if (!StringUtils.isEmpty(setCode) && !setCode.equals(CardEdition.UNKNOWN_CODE)) {
|
||||||
|
if (!StringUtils.isEmpty(cn) && !cn.equals(IPaperCard.NO_COLLECTOR_NUMBER)) {
|
||||||
|
result.add(setCode + "/" + cn + "_" + name);
|
||||||
|
}
|
||||||
|
result.add(setCode + "/" + name);
|
||||||
|
}
|
||||||
|
result.add(name);
|
||||||
|
} else if (type == ImageType.Card) {
|
||||||
|
if (!StringUtils.isEmpty(setCode) && !setCode.equals(CardEdition.UNKNOWN_CODE)) {
|
||||||
|
if (!StringUtils.isEmpty(cn) && !cn.equals(IPaperCard.NO_COLLECTOR_NUMBER)) {
|
||||||
|
result.add(setCode + "/" + cn + "_" + name + "." + art.filename);
|
||||||
|
}
|
||||||
|
result.add(setCode + "/" + name + "." + art.filename);
|
||||||
|
}
|
||||||
|
result.add(name + "." + art.filename);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pair<String, String> getDownloadUrl(ArtStyle art) {
|
||||||
|
if (custom) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (type == ImageType.Token && ImageKeys.HIDDEN_CARD.equals(name)) {
|
||||||
|
return Pair.of("hidden.png", "https://cards.scryfall.io/back.png");
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(collectorNumber) || collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Scryfall only for Cards or Tokens
|
||||||
|
if (type != ImageType.Card && type != ImageType.Token) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(setCode) || setCode.equals(CardEdition.UNKNOWN_CODE)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
CardEdition edition = StaticData.instance().getCardEdition(setCode);
|
||||||
|
if (edition == null || edition.getType() == CardEdition.Type.CUSTOM_SET) return null;
|
||||||
|
// differ token code
|
||||||
|
String setCode = type == ImageType.Card ? edition.getScryfallCode() : edition.getTokensCode();
|
||||||
|
String langCode = edition.getCardsLangCode();
|
||||||
|
String faceParam = "";
|
||||||
|
switch(state) {
|
||||||
|
case Modal:
|
||||||
|
case Secondary:
|
||||||
|
case Transformed:
|
||||||
|
faceParam = "&face=back";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
faceParam = "&face=front";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
String ext = art.scryfall == "png" ? ".png" : ".jpg";
|
||||||
|
String filepath = setCode + "/" + collectorNumber + "_" + name + ext;
|
||||||
|
// TODO make scryfall art_crop of split cards separate
|
||||||
|
String collectorNumberEncoded;
|
||||||
|
try {
|
||||||
|
// encode with Charset isn't supported on Android
|
||||||
|
collectorNumberEncoded = URLEncoder.encode(collectorNumber, "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
collectorNumberEncoded = collectorNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = ImageKeys.URL_PIC_SCRYFALL_DOWNLOAD + String.format(
|
||||||
|
"%s/%s/%s?format=image&version=%s%s", setCode, collectorNumberEncoded, langCode, art.scryfall, faceParam
|
||||||
|
);
|
||||||
|
return Pair.of(filepath, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
forge-core/src/main/java/forge/item/ImageType.java
Normal file
12
forge-core/src/main/java/forge/item/ImageType.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package forge.item;
|
||||||
|
|
||||||
|
public enum ImageType {
|
||||||
|
Card,
|
||||||
|
Token,
|
||||||
|
Booster,
|
||||||
|
FatPack,
|
||||||
|
BoosterBox,
|
||||||
|
Precon,
|
||||||
|
TournamentPack,
|
||||||
|
;
|
||||||
|
}
|
||||||
@@ -26,10 +26,9 @@ import forge.util.Localizer;
|
|||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
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 +38,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 +55,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 +88,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 +146,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);
|
||||||
}
|
}
|
||||||
public PaperCard getColorIDVersion(Set<String> colors) {
|
return flaglessVersion;
|
||||||
if (colors == null && this.colorID == null)
|
}
|
||||||
|
public PaperCard copyWithFlags(Map<String, String> flags) {
|
||||||
|
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 +179,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 +201,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 +262,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 +274,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 +306,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 +338,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();
|
||||||
@@ -347,17 +347,46 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
if (pc == null) {
|
if (pc == null) {
|
||||||
pc = StaticData.instance().getVariantCards().getCard(name, edition, artIndex);
|
pc = StaticData.instance().getVariantCards().getCard(name, edition, artIndex);
|
||||||
if (pc == null) {
|
if (pc == null) {
|
||||||
throw new IOException(TextUtil.concatWithSpace("Card", name, "not found"));
|
System.out.println("PaperCard: " + name + " not found with set and index " + edition + ", " + artIndex);
|
||||||
|
pc = readObjectAlternate(name, edition);
|
||||||
|
if (pc == null) {
|
||||||
|
throw new IOException(TextUtil.concatWithSpace("Card", name, "not found with set and index", edition, Integer.toString(artIndex)));
|
||||||
|
}
|
||||||
|
System.out.println("Alternate object found: " + pc.getName() + ", " + pc.getEdition() + ", " + pc.getArtIndex());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rules = pc.getRules();
|
rules = pc.getRules();
|
||||||
rarity = pc.getRarity();
|
rarity = pc.getRarity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IPaperCard readObjectAlternate(String name, String edition) throws ClassNotFoundException, IOException {
|
||||||
|
IPaperCard pc = StaticData.instance().getCommonCards().getCard(name, edition);
|
||||||
|
if (pc == null) {
|
||||||
|
pc = StaticData.instance().getVariantCards().getCard(name, edition);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pc == null) {
|
||||||
|
pc = StaticData.instance().getCommonCards().getCard(name);
|
||||||
|
if (pc == null) {
|
||||||
|
pc = StaticData.instance().getVariantCards().getCard(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
String normalizedName = StringUtils.stripAccents(name);
|
||||||
String imageKey = ImageKeys.CARD_PREFIX + noramlizedName + CardDb.NameSetSeparator
|
String imageKey = ImageKeys.CARD_PREFIX + normalizedName + CardDb.NameSetSeparator
|
||||||
+ edition + CardDb.NameSetSeparator + artIndex;
|
+ edition + CardDb.NameSetSeparator + artIndex;
|
||||||
if (altState) {
|
if (altState) {
|
||||||
imageKey += ImageKeys.BACKFACE_POSTFIX;
|
imageKey += ImageKeys.BACKFACE_POSTFIX;
|
||||||
@@ -493,4 +522,111 @@ public class PaperCard implements Comparable<IPaperCard>, InventoryItemFromSet,
|
|||||||
public boolean isRebalanced() {
|
public boolean isRebalanced() {
|
||||||
return StaticData.instance().isRebalanced(name);
|
return StaticData.instance().isRebalanced(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCollectorNumberSuffix(CardStateName state) {
|
||||||
|
if (this.collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (getRules().getSplitType() == CardSplitType.Meld) {
|
||||||
|
return state == CardStateName.Meld ? "b" : "a";
|
||||||
|
}
|
||||||
|
switch(state) {
|
||||||
|
case SpecializeB:
|
||||||
|
return "b";
|
||||||
|
case SpecializeG:
|
||||||
|
return "g";
|
||||||
|
case SpecializeR:
|
||||||
|
return "r";
|
||||||
|
case SpecializeU:
|
||||||
|
return "u";
|
||||||
|
case SpecializeW:
|
||||||
|
return "w";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)) {
|
||||||
|
++idx;
|
||||||
|
if (!t.collectorNumber().equals(collectorNumber)) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
// TODO make better image file names when collector number is known
|
||||||
if (imageFileName == null) {
|
// for the right index, we need to count the ones with wrong collector number too
|
||||||
// This shouldn't really happen. We can just use the normalized name again for the base image name
|
this.imageFileName.add(String.format("%s|%s|%s|%d", imageFileName, edition.getCode(), collectorNumber, idx));
|
||||||
this.imageFileName.add(makeTokenFileName(c, edition0));
|
}
|
||||||
|
this.artIndex = this.imageFileName.size();
|
||||||
|
} else if (null == edition || CardEdition.UNKNOWN == edition) {
|
||||||
|
this.imageFileName.add(imageFileName);
|
||||||
} else {
|
} else {
|
||||||
String formatEdition = null == edition || CardEdition.UNKNOWN == edition ? "" : "_" + edition.getCode().toLowerCase();
|
// Fallback if CollectorNumber is not used
|
||||||
|
this.imageFileName.add(String.format("%s|%s", imageFileName, edition.getCode()));
|
||||||
this.imageFileName.add(String.format("%s%s", imageFileName, formatEdition));
|
|
||||||
for (int idx = 2; idx <= this.artIndex; idx++) {
|
|
||||||
this.imageFileName.add(String.format("%s%d%s", imageFileName, idx, formatEdition));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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() {
|
||||||
|
if (collectorNumber.isEmpty())
|
||||||
return IPaperCard.NO_COLLECTOR_NUMBER;
|
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() {
|
||||||
|
|||||||
@@ -401,24 +401,31 @@ public class BoosterGenerator {
|
|||||||
System.out.println(numCards + " of type " + slotType);
|
System.out.println(numCards + " of type " + slotType);
|
||||||
|
|
||||||
// For cards that end in '+', attempt to convert this card to foil.
|
// For cards that end in '+', attempt to convert this card to foil.
|
||||||
boolean convertCardFoil = slotType.endsWith("+");
|
boolean convertAllToFoil = slotType.endsWith("+");
|
||||||
if (convertCardFoil) {
|
if (convertAllToFoil) {
|
||||||
slotType = slotType.substring(0, slotType.length() - 1);
|
slotType = slotType.substring(0, slotType.length() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unpack Base
|
|
||||||
BoosterSlot boosterSlot = boosterSlots.get(slotType);
|
BoosterSlot boosterSlot = boosterSlots.get(slotType);
|
||||||
String determineSheet = boosterSlot.replaceSlot();
|
Map<String, Integer> slotReplacementCount = bulkSlotReplacement(boosterSlot, numCards);
|
||||||
|
|
||||||
|
List<PaperCard> paperCards = Lists.newArrayList();
|
||||||
|
for(Map.Entry<String, Integer> entry : slotReplacementCount.entrySet()) {
|
||||||
|
String determineSheet = entry.getKey();
|
||||||
|
int numCardsToGenerate = entry.getValue();
|
||||||
|
|
||||||
|
if (determineSheet == null || determineSheet.isEmpty() || numCardsToGenerate == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the sheet ends with a '+', convert all cards in replacement section to foil
|
||||||
|
boolean convertThisToFoil = false;
|
||||||
if (determineSheet.endsWith("+")) {
|
if (determineSheet.endsWith("+")) {
|
||||||
determineSheet = determineSheet.substring(0, determineSheet.length() - 1);
|
determineSheet = determineSheet.substring(0, determineSheet.length() - 1);
|
||||||
convertCardFoil = true;
|
convertThisToFoil = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
String setCode = template.getEdition();
|
String setCode = template.getEdition();
|
||||||
|
|
||||||
// Ok, so we have a sheet now. Most should be standard sheets, but some named edition sheets
|
|
||||||
List<PaperCard> paperCards;
|
|
||||||
PrintSheet ps;
|
PrintSheet ps;
|
||||||
try {
|
try {
|
||||||
// Apply the edition to the sheet name by default. We'll try again if thats not a real sheet
|
// Apply the edition to the sheet name by default. We'll try again if thats not a real sheet
|
||||||
@@ -426,21 +433,36 @@ public class BoosterGenerator {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ps = getPrintSheet(determineSheet);
|
ps = getPrintSheet(determineSheet);
|
||||||
}
|
}
|
||||||
if (convertCardFoil) {
|
if (convertAllToFoil || convertThisToFoil) {
|
||||||
paperCards = Lists.newArrayList();
|
for (PaperCard pc : ps.random(numCardsToGenerate, true)) {
|
||||||
for(PaperCard pc : ps.random(numCards, true)) {
|
|
||||||
paperCards.add(pc.getFoiled());
|
paperCards.add(pc.getFoiled());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
paperCards = ps.random(numCards, true);
|
paperCards.addAll(ps.random(numCardsToGenerate, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
result.addAll(paperCards);
|
result.addAll(paperCards);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Map<String, Integer> bulkSlotReplacement(BoosterSlot boosterSlot, int numCards) {
|
||||||
|
Map<String, Integer> slotReplacementCount = new HashMap<>();
|
||||||
|
|
||||||
|
for(int i = 0; i < numCards; i++) {
|
||||||
|
String determineSheet = boosterSlot.replaceSlot();
|
||||||
|
if (slotReplacementCount.containsKey(determineSheet)) {
|
||||||
|
slotReplacementCount.put(determineSheet, slotReplacementCount.get(determineSheet) + 1);
|
||||||
|
} else {
|
||||||
|
slotReplacementCount.put(determineSheet, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return slotReplacementCount;
|
||||||
|
}
|
||||||
|
|
||||||
private static void ensureGuaranteedCardInBooster(List<PaperCard> result, SealedTemplate template, String boosterMustContain) {
|
private static void ensureGuaranteedCardInBooster(List<PaperCard> result, SealedTemplate template, String boosterMustContain) {
|
||||||
// First, see if there's already a card of the given type
|
// First, see if there's already a card of the given type
|
||||||
String[] types = TextUtil.split(boosterMustContain, ' ');
|
String[] types = TextUtil.split(boosterMustContain, ' ');
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import forge.item.IPaperCard;
|
|||||||
import forge.item.PaperCard;
|
import forge.item.PaperCard;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
|
||||||
public class ImageUtil {
|
public class ImageUtil {
|
||||||
public static float getNearestHQSize(float baseSize, float actualSize) {
|
public static float getNearestHQSize(float baseSize, float actualSize) {
|
||||||
//get nearest power of actualSize to baseSize so that the image renders good
|
//get nearest power of actualSize to baseSize so that the image renders good
|
||||||
@@ -100,7 +102,7 @@ public class ImageUtil {
|
|||||||
|
|
||||||
if (includeSet) {
|
if (includeSet) {
|
||||||
String editionAliased = isDownloadUrl ? StaticData.instance().getEditions().getCode2ByCode(edition) : ImageKeys.getSetFolder(edition);
|
String editionAliased = isDownloadUrl ? StaticData.instance().getEditions().getCode2ByCode(edition) : ImageKeys.getSetFolder(edition);
|
||||||
if (editionAliased == "") //FIXME: Custom Cards Workaround
|
if (editionAliased.isEmpty()) //FIXME: Custom Cards Workaround
|
||||||
editionAliased = edition;
|
editionAliased = edition;
|
||||||
return TextUtil.concatNoSpace(editionAliased, "/", fname);
|
return TextUtil.concatNoSpace(editionAliased, "/", fname);
|
||||||
} else {
|
} else {
|
||||||
@@ -164,7 +166,7 @@ public class ImageUtil {
|
|||||||
|
|
||||||
public static String getScryfallDownloadUrl(PaperCard cp, String face, String setCode, String langCode, boolean useArtCrop, boolean hyphenateAlchemy){
|
public static String getScryfallDownloadUrl(PaperCard cp, String face, String setCode, String langCode, boolean useArtCrop, boolean hyphenateAlchemy){
|
||||||
String editionCode;
|
String editionCode;
|
||||||
if ((setCode != null) && (setCode.length() > 0))
|
if (setCode != null && !setCode.isEmpty())
|
||||||
editionCode = setCode;
|
editionCode = setCode;
|
||||||
else
|
else
|
||||||
editionCode = cp.getEdition().toLowerCase();
|
editionCode = cp.getEdition().toLowerCase();
|
||||||
@@ -192,8 +194,33 @@ public class ImageUtil {
|
|||||||
String faceParam = "";
|
String faceParam = "";
|
||||||
if (cp.getRules().getOtherPart() != null) {
|
if (cp.getRules().getOtherPart() != null) {
|
||||||
faceParam = (face.equals("back") ? "&face=back" : "&face=front");
|
faceParam = (face.equals("back") ? "&face=back" : "&face=front");
|
||||||
|
} else if (cp.getRules().getSplitType() == CardSplitType.Meld
|
||||||
|
&& !cardCollectorNumber.endsWith("a")
|
||||||
|
&& !cardCollectorNumber.endsWith("b")) {
|
||||||
|
// Only the bottom half of a meld card shares a collector number.
|
||||||
|
// Hanweir Garrison EMN already has a appended.
|
||||||
|
cardCollectorNumber += face.equals("back") ? "b" : "a";
|
||||||
}
|
}
|
||||||
return String.format("%s/%s/%s?format=image&version=%s%s", editionCode, cardCollectorNumber,
|
|
||||||
|
String cardCollectorNumberEncoded;
|
||||||
|
try {
|
||||||
|
cardCollectorNumberEncoded = URLEncoder.encode(cardCollectorNumber, "UTF-8");
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Unlikely, for the possibility that "UTF-8" is not supported.
|
||||||
|
System.err.println("UTF-8 encoding not supported on this device.");
|
||||||
|
cardCollectorNumberEncoded = cardCollectorNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
return String.format("%s/%s/%s?format=image&version=%s%s", editionCode, cardCollectorNumberEncoded,
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,6 +193,8 @@ public class ForgeScript {
|
|||||||
return sa.isCrew();
|
return sa.isCrew();
|
||||||
} else if (property.equals("Saddle")) {
|
} else if (property.equals("Saddle")) {
|
||||||
return sa.isKeyword(Keyword.SADDLE);
|
return sa.isKeyword(Keyword.SADDLE);
|
||||||
|
} else if (property.equals("Station")) {
|
||||||
|
return sa.isKeyword(Keyword.STATION);
|
||||||
}else if (property.equals("Cycling")) {
|
}else if (property.equals("Cycling")) {
|
||||||
return sa.isCycling();
|
return sa.isCycling();
|
||||||
} else if (property.equals("Dash")) {
|
} else if (property.equals("Dash")) {
|
||||||
@@ -225,6 +207,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 +227,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,10 +1207,16 @@ 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) {
|
||||||
|
for (Map.Entry<Player, List<Pair<Card, Integer>>> e : countersAddedThisTurn.row(type).entrySet()) {
|
||||||
if (e.getKey().isValid(validPlayer.split(","), sourceController, source, ctb)) {
|
if (e.getKey().isValid(validPlayer.split(","), sourceController, source, ctb)) {
|
||||||
for (Pair<Card, Integer> p : e.getValue()) {
|
for (Pair<Card, Integer> p : e.getValue()) {
|
||||||
if (p.getKey().isValid(validCard.split(","), sourceController, source, ctb)) {
|
if (p.getKey().isValid(validCard.split(","), sourceController, source, ctb)) {
|
||||||
@@ -1218,20 +1225,28 @@ public class Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
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 (List<Pair<Card, Integer>> l : countersAddedThisTurn.row(type).values()) {
|
||||||
for (Pair<Card, Integer> p : l) {
|
for (Pair<Card, Integer> p : l) {
|
||||||
if (p.getKey().equalsWithGameTimestamp(card)) {
|
if (p.getKey().equalsWithGameTimestamp(card)) {
|
||||||
result += p.getValue();
|
result += p.getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -519,6 +519,7 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
card.setZone(zoneTo);
|
card.setZone(zoneTo);
|
||||||
}
|
}
|
||||||
|
copied.clearMergedCards();
|
||||||
} else {
|
} else {
|
||||||
storeChangesZoneAll(copied, zoneFrom, zoneTo, params);
|
storeChangesZoneAll(copied, zoneFrom, zoneTo, params);
|
||||||
// "enter the battlefield as a copy" - apply code here
|
// "enter the battlefield as a copy" - apply code here
|
||||||
@@ -542,26 +543,26 @@ 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();
|
||||||
|
|
||||||
|
// we don't want always trigger before counters are placed
|
||||||
|
game.getTriggerHandler().suppressMode(TriggerType.Always);
|
||||||
|
// Need to apply any static effects to produce correct triggers
|
||||||
|
checkStaticAbilities();
|
||||||
|
|
||||||
// needed for counters + ascend
|
// needed for counters + ascend
|
||||||
if (!suppress && toBattlefield) {
|
if (!suppress && toBattlefield) {
|
||||||
game.getTriggerHandler().registerActiveTrigger(copied, false);
|
game.getTriggerHandler().registerActiveTrigger(copied, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!table.isEmpty()) {
|
|
||||||
// we don't want always trigger before counters are placed
|
|
||||||
game.getTriggerHandler().suppressMode(TriggerType.Always);
|
|
||||||
// Need to apply any static effects to produce correct triggers
|
|
||||||
checkStaticAbilities();
|
|
||||||
// do ETB counters after zone add
|
// do ETB counters after zone add
|
||||||
table.replaceCounterEffect(game, null, true, true, params);
|
table.replaceCounterEffect(game, null, true, true, params);
|
||||||
|
|
||||||
game.getTriggerHandler().clearSuppression(TriggerType.Always);
|
game.getTriggerHandler().clearSuppression(TriggerType.Always);
|
||||||
}
|
|
||||||
|
|
||||||
// update static abilities after etb counters have been placed
|
// update static abilities after etb counters have been placed
|
||||||
checkStaticAbilities();
|
checkStaticAbilities();
|
||||||
@@ -645,7 +646,8 @@ public class GameAction {
|
|||||||
// Ask controller if it wants to be on top or bottom of other meld.
|
// Ask controller if it wants to be on top or bottom of other meld.
|
||||||
unmeldPosition++;
|
unmeldPosition++;
|
||||||
}
|
}
|
||||||
changeZone(null, zoneTo, unmeld, position, cause, params);
|
unmeld = changeZone(null, zoneTo, unmeld, position, cause, params);
|
||||||
|
storeChangesZoneAll(unmeld, zoneFrom, zoneTo, params);
|
||||||
}
|
}
|
||||||
} else if (toBattlefield) {
|
} else if (toBattlefield) {
|
||||||
for (Player p : game.getPlayers()) {
|
for (Player p : game.getPlayers()) {
|
||||||
@@ -797,7 +799,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 +809,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 +1075,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 +1107,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 +1121,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 +1168,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 +1465,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 +1512,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 +1531,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 +1571,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 +1630,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 +1652,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);
|
||||||
@@ -2027,7 +2040,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 +2049,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 +2430,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 +2786,36 @@ 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());
|
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 +2824,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 +2837,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) {
|
|
||||||
if (type == AbilityRecordType.Spell) {
|
|
||||||
SpellAbility firstAbility = state.getFirstAbility();
|
|
||||||
if (firstAbility != null && firstAbility.isSpell()) {
|
|
||||||
// TODO might remove when Enchant Keyword is refactored
|
|
||||||
System.err.println(state.getName() + " already has Spell using mana cost");
|
|
||||||
}
|
}
|
||||||
|
String cost = mapParams.get("Cost");
|
||||||
|
if (cost != null) {
|
||||||
|
return new Cost(cost, type == AbilityRecordType.Ability);
|
||||||
|
}
|
||||||
|
if (type == AbilityRecordType.Spell) {
|
||||||
// for a Spell if no Cost is used, use the card states ManaCost
|
// for a Spell if no Cost is used, use the card states ManaCost
|
||||||
abCost = new Cost(state.getManaCost(), false);
|
return new Cost(state.getManaCost(), false);
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("AbilityFactory : getAbility -- no Cost in " + state.getName());
|
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"),
|
||||||
@@ -131,7 +130,6 @@ public enum AbilityKey {
|
|||||||
SourceSA("SourceSA"),
|
SourceSA("SourceSA"),
|
||||||
SpellAbility("SpellAbility"),
|
SpellAbility("SpellAbility"),
|
||||||
SpellAbilityTargets("SpellAbilityTargets"),
|
SpellAbilityTargets("SpellAbilityTargets"),
|
||||||
StackInstance("StackInstance"),
|
|
||||||
StackSa("StackSa"),
|
StackSa("StackSa"),
|
||||||
SurveilNum("SurveilNum"),
|
SurveilNum("SurveilNum"),
|
||||||
Target("Target"),
|
Target("Target"),
|
||||||
@@ -141,7 +139,7 @@ public enum AbilityKey {
|
|||||||
Valiant("Valiant"),
|
Valiant("Valiant"),
|
||||||
Won("Won"),
|
Won("Won"),
|
||||||
|
|
||||||
// below used across different Replacements, don't reuse
|
// below shared across different Replacements, don't reuse
|
||||||
InternalTriggerTable("InternalTriggerTable"),
|
InternalTriggerTable("InternalTriggerTable"),
|
||||||
SimultaneousETB("SimultaneousETB"); // for CR 614.13c
|
SimultaneousETB("SimultaneousETB"); // for CR 614.13c
|
||||||
|
|
||||||
|
|||||||
@@ -663,7 +663,7 @@ public class AbilityUtils {
|
|||||||
Object o = root.getTriggeringObject(AbilityKey.fromString(calcX[0].substring(9)));
|
Object o = root.getTriggeringObject(AbilityKey.fromString(calcX[0].substring(9)));
|
||||||
val = o instanceof Player ? playerXProperty((Player) o, calcX[1], card, ability) : 0;
|
val = o instanceof Player ? playerXProperty((Player) o, calcX[1], card, ability) : 0;
|
||||||
}
|
}
|
||||||
else if (calcX[0].equals("TriggeredSpellAbility") || calcX[0].equals("TriggeredStackInstance") || calcX[0].equals("SpellTargeted")) {
|
else if (calcX[0].equals("TriggeredSpellAbility") || calcX[0].equals("SpellTargeted")) {
|
||||||
final SpellAbility sat = Iterables.getFirst(getDefinedSpellAbilities(card, calcX[0], sa), null);
|
final SpellAbility sat = Iterables.getFirst(getDefinedSpellAbilities(card, calcX[0], sa), null);
|
||||||
val = sat == null ? 0 : xCount(sat.getHostCard(), calcX[1], sat);
|
val = sat == null ? 0 : xCount(sat.getHostCard(), calcX[1], sat);
|
||||||
}
|
}
|
||||||
@@ -1281,8 +1281,6 @@ public class AbilityUtils {
|
|||||||
final Object o = root.getTriggeringObject(AbilityKey.fromString(triggeringType));
|
final Object o = root.getTriggeringObject(AbilityKey.fromString(triggeringType));
|
||||||
if (o instanceof SpellAbility) {
|
if (o instanceof SpellAbility) {
|
||||||
s = (SpellAbility) o;
|
s = (SpellAbility) o;
|
||||||
} else if (o instanceof SpellAbilityStackInstance) {
|
|
||||||
s = ((SpellAbilityStackInstance) o).getSpellAbility();
|
|
||||||
}
|
}
|
||||||
} else if (defined.endsWith("Targeted") && sa instanceof SpellAbility) {
|
} else if (defined.endsWith("Targeted") && sa instanceof SpellAbility) {
|
||||||
final List<TargetChoices> targets = defined.startsWith("This") ? Arrays.asList(((SpellAbility)sa).getTargets()) : ((SpellAbility)sa).getAllTargetChoices();
|
final List<TargetChoices> targets = defined.startsWith("This") ? Arrays.asList(((SpellAbility)sa).getTargets()) : ((SpellAbility)sa).getAllTargetChoices();
|
||||||
@@ -1366,7 +1364,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 +1619,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) {
|
||||||
@@ -1684,11 +1683,11 @@ public class AbilityUtils {
|
|||||||
return doXMath(x, expr, c, ctb);
|
return doXMath(x, expr, c, ctb);
|
||||||
} else if (TriggerType.SpellCast.equals(t.getMode())) {
|
} else if (TriggerType.SpellCast.equals(t.getMode())) {
|
||||||
// Cast Trigger like Hydroid Krasis
|
// Cast Trigger like Hydroid Krasis
|
||||||
SpellAbilityStackInstance castSI = (SpellAbilityStackInstance) root.getTriggeringObject(AbilityKey.StackInstance);
|
SpellAbility castSA = (SpellAbility) root.getTriggeringObject(AbilityKey.SpellAbility);
|
||||||
if (castSI == null || castSI.getSpellAbility().getXManaCostPaid() == null) {
|
if (castSA == null || castSA.getXManaCostPaid() == null) {
|
||||||
return doXMath(0, expr, c, ctb);
|
return doXMath(0, expr, c, ctb);
|
||||||
}
|
}
|
||||||
return doXMath(castSI.getSpellAbility().getXManaCostPaid(), expr, c, ctb);
|
return doXMath(castSA.getXManaCostPaid(), expr, c, ctb);
|
||||||
} else if (TriggerType.Cycled.equals(t.getMode())) {
|
} else if (TriggerType.Cycled.equals(t.getMode())) {
|
||||||
SpellAbility cycleSA = (SpellAbility) sa.getTriggeringObject(AbilityKey.Cause);
|
SpellAbility cycleSA = (SpellAbility) sa.getTriggeringObject(AbilityKey.Cause);
|
||||||
if (cycleSA == null || cycleSA.getXManaCostPaid() == null) {
|
if (cycleSA == null || cycleSA.getXManaCostPaid() == null) {
|
||||||
@@ -1786,11 +1785,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 +1807,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 +1852,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 +1956,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 +2214,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 +2223,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 +2270,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 +2322,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 +2411,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 +2482,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 +2711,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 +2769,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 +2914,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 +3398,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 +3429,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();
|
||||||
@@ -3780,6 +3749,10 @@ public class AbilityUtils {
|
|||||||
return Aggregates.max(paidList, Card::getNetToughness);
|
return Aggregates.max(paidList, Card::getNetToughness);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.startsWith("TapPowerValue")) {
|
||||||
|
return CardLists.getTotalPower(paidList, ctb);
|
||||||
|
}
|
||||||
|
|
||||||
if (string.startsWith("SumToughness")) {
|
if (string.startsWith("SumToughness")) {
|
||||||
return Aggregates.sum(paidList, Card::getNetToughness);
|
return Aggregates.sum(paidList, Card::getNetToughness);
|
||||||
}
|
}
|
||||||
@@ -3788,6 +3761,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 +3794,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");
|
||||||
|
if (upkeep) {
|
||||||
|
sb.append(" next upkeep.");
|
||||||
|
} else {
|
||||||
sb.append(" next end step.");
|
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()) {
|
||||||
|
|||||||
@@ -72,8 +72,8 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
|||||||
// 2. prepare new target choices
|
// 2. prepare new target choices
|
||||||
SpellAbilityStackInstance replaceIn = chosenTarget.getKey();
|
SpellAbilityStackInstance replaceIn = chosenTarget.getKey();
|
||||||
GameObject oldTarget = chosenTarget.getValue();
|
GameObject oldTarget = chosenTarget.getValue();
|
||||||
TargetChoices oldTargetBlock = replaceIn.getTargetChoices();
|
TargetChoices newTargetBlock = replaceIn.getTargetChoices();
|
||||||
TargetChoices newTargetBlock = oldTargetBlock.clone();
|
TargetChoices oldTargetBlock = newTargetBlock.clone();
|
||||||
// gets the divided value from old target
|
// gets the divided value from old target
|
||||||
Integer div = oldTargetBlock.getDividedValue(oldTarget);
|
Integer div = oldTargetBlock.getDividedValue(oldTarget);
|
||||||
// 3. test if updated choices would be correct.
|
// 3. test if updated choices would be correct.
|
||||||
@@ -87,7 +87,7 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
|||||||
if (div != null) {
|
if (div != null) {
|
||||||
newTargetBlock.addDividedAllocation(newTarget, div);
|
newTargetBlock.addDividedAllocation(newTarget, div);
|
||||||
}
|
}
|
||||||
replaceIn.updateTarget(newTargetBlock, sa.getHostCard());
|
replaceIn.updateTarget(oldTargetBlock, sa.getHostCard());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
while (changingTgtSI != null) {
|
while (changingTgtSI != null) {
|
||||||
@@ -104,25 +104,26 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
|||||||
if (candidates.isEmpty()) {
|
if (candidates.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
changingTgtSA.resetTargets();
|
|
||||||
GameEntity choice = Aggregates.random(candidates);
|
GameEntity choice = Aggregates.random(candidates);
|
||||||
|
TargetChoices oldTarget = changingTgtSA.getTargets();
|
||||||
|
changingTgtSA.resetTargets();
|
||||||
changingTgtSA.getTargets().add(choice);
|
changingTgtSA.getTargets().add(choice);
|
||||||
if (changingTgtSA.isDividedAsYouChoose()) {
|
if (changingTgtSA.isDividedAsYouChoose()) {
|
||||||
changingTgtSA.addDividedAllocation(choice, div);
|
changingTgtSA.addDividedAllocation(choice, div);
|
||||||
}
|
}
|
||||||
|
changingTgtSI.updateTarget(oldTarget, sa.getHostCard());
|
||||||
changingTgtSI.updateTarget(changingTgtSA.getTargets(), sa.getHostCard());
|
|
||||||
}
|
}
|
||||||
else if (sa.hasParam("DefinedMagnet")) {
|
else if (sa.hasParam("DefinedMagnet")) {
|
||||||
GameObject newTarget = Iterables.getFirst(getDefinedCardsOrTargeted(sa, "DefinedMagnet"), null);
|
GameObject newTarget = Iterables.getFirst(getDefinedCardsOrTargeted(sa, "DefinedMagnet"), null);
|
||||||
if (newTarget != null && changingTgtSA.canTarget(newTarget)) {
|
if (newTarget != null && changingTgtSA.canTarget(newTarget)) {
|
||||||
int div = changingTgtSA.getTotalDividedValue();
|
int div = changingTgtSA.getTotalDividedValue();
|
||||||
|
TargetChoices oldTarget = changingTgtSA.getTargets();
|
||||||
changingTgtSA.resetTargets();
|
changingTgtSA.resetTargets();
|
||||||
changingTgtSA.getTargets().add(newTarget);
|
changingTgtSA.getTargets().add(newTarget);
|
||||||
changingTgtSI.updateTarget(changingTgtSA.getTargets(), sa.getHostCard());
|
|
||||||
if (changingTgtSA.isDividedAsYouChoose()) {
|
if (changingTgtSA.isDividedAsYouChoose()) {
|
||||||
changingTgtSA.addDividedAllocation(newTarget, div);
|
changingTgtSA.addDividedAllocation(newTarget, div);
|
||||||
}
|
}
|
||||||
|
changingTgtSI.updateTarget(oldTarget, sa.getHostCard());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Update targets, with a potential new target
|
// Update targets, with a potential new target
|
||||||
@@ -132,8 +133,9 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
|||||||
source = changingTgtSA.getTargetCard();
|
source = changingTgtSA.getTargetCard();
|
||||||
}
|
}
|
||||||
Predicate<GameObject> filter = sa.hasParam("TargetRestriction") ? GameObjectPredicates.restriction(sa.getParam("TargetRestriction").split(","), activator, source, sa) : null;
|
Predicate<GameObject> filter = sa.hasParam("TargetRestriction") ? GameObjectPredicates.restriction(sa.getParam("TargetRestriction").split(","), activator, source, sa) : null;
|
||||||
TargetChoices newTarget = chooser.getController().chooseNewTargetsFor(changingTgtSA, filter, false);
|
TargetChoices oldTarget = changingTgtSA.getTargets();
|
||||||
changingTgtSI.updateTarget(newTarget, sa.getHostCard());
|
chooser.getController().chooseNewTargetsFor(changingTgtSA, filter, false);
|
||||||
|
changingTgtSI.updateTarget(oldTarget, sa.getHostCard());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
changingTgtSI = changingTgtSI.getSubInstance();
|
changingTgtSI = changingTgtSI.getSubInstance();
|
||||||
|
|||||||
@@ -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
|
// look at the exiled card
|
||||||
movedCard.addMayLookTemp(activator);
|
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
|
// look at the exiled card
|
||||||
movedCard.addMayLookTemp(sa.getActivatingPlayer());
|
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,16 +59,15 @@ 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 {
|
} else {
|
||||||
@@ -67,7 +76,6 @@ public class ChooseColorEffect extends SpellAbilityEffect {
|
|||||||
} else {
|
} else {
|
||||||
prompt = Localizer.getInstance().getMessage("lblChooseNColors", Lang.getNumeral(cntMax));
|
prompt = Localizer.getInstance().getMessage("lblChooseNColors", Lang.getNumeral(cntMax));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Player noNotify = p;
|
Player noNotify = p;
|
||||||
if (sa.hasParam("Random")) {
|
if (sa.hasParam("Random")) {
|
||||||
String choice;
|
String choice;
|
||||||
|
|||||||
@@ -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,8 +632,20 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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);
|
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,7 +101,28 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
|||||||
boolean rememberAmount = sa.hasParam("RememberAmount");
|
boolean rememberAmount = sa.hasParam("RememberAmount");
|
||||||
|
|
||||||
int totalRemoved = 0;
|
int totalRemoved = 0;
|
||||||
|
CardCollectionView srcCards;
|
||||||
|
|
||||||
|
String typeforPrompt = counterType == null ? "" : counterType.getName();
|
||||||
|
String title = Localizer.getInstance().getMessage("lblChooseCardsToTakeTargetCounters", typeforPrompt);
|
||||||
|
title = title.replace(" ", " ");
|
||||||
|
if (sa.hasParam("Choices")) {
|
||||||
|
ZoneType choiceZone = sa.hasParam("ChoiceZone") ? ZoneType.smartValueOf(sa.getParam("ChoiceZone"))
|
||||||
|
: ZoneType.Battlefield;
|
||||||
|
|
||||||
|
CardCollection choices = CardLists.getValidCards(game.getCardsIn(choiceZone), sa.getParam("Choices"),
|
||||||
|
activator, card, sa);
|
||||||
|
|
||||||
|
int min = 1;
|
||||||
|
int max = 1;
|
||||||
|
if (sa.hasParam("ChoiceOptional")) {
|
||||||
|
min = 0;
|
||||||
|
max = choices.size();
|
||||||
|
}
|
||||||
|
Map<String, Object> params = Maps.newHashMap();
|
||||||
|
params.put("CounterType", counterType);
|
||||||
|
srcCards = pc.chooseCardsForEffect(choices, sa, title, min, max, min == 0, params);
|
||||||
|
} else {
|
||||||
for (final Player tgtPlayer : getTargetPlayers(sa)) {
|
for (final Player tgtPlayer : getTargetPlayers(sa)) {
|
||||||
if (!tgtPlayer.isInGame()) {
|
if (!tgtPlayer.isInGame()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -123,28 +144,6 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollectionView srcCards;
|
|
||||||
|
|
||||||
String typeforPrompt = counterType == null ? "" : counterType.getName();
|
|
||||||
String title = Localizer.getInstance().getMessage("lblChooseCardsToTakeTargetCounters", typeforPrompt);
|
|
||||||
title = title.replace(" ", " ");
|
|
||||||
if (sa.hasParam("Choices") && counterType != null) {
|
|
||||||
ZoneType choiceZone = sa.hasParam("ChoiceZone") ? ZoneType.smartValueOf(sa.getParam("ChoiceZone"))
|
|
||||||
: ZoneType.Battlefield;
|
|
||||||
|
|
||||||
CardCollection choices = CardLists.getValidCards(game.getCardsIn(choiceZone), sa.getParam("Choices"),
|
|
||||||
activator, card, sa);
|
|
||||||
|
|
||||||
int min = 1;
|
|
||||||
int max = 1;
|
|
||||||
if (sa.hasParam("ChoiceOptional")) {
|
|
||||||
min = 0;
|
|
||||||
max = choices.size();
|
|
||||||
}
|
|
||||||
Map<String, Object> params = Maps.newHashMap();
|
|
||||||
params.put("CounterType", counterType);
|
|
||||||
srcCards = pc.chooseCardsForEffect(choices, sa, title, min, max, min == 0, params);
|
|
||||||
} else {
|
|
||||||
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,7 +137,6 @@ 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)) {
|
||||||
@@ -230,9 +226,9 @@ public class DiscardEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
Player chooser = p;
|
Player chooser = p;
|
||||||
if (mode.endsWith("YouChoose")) {
|
if (mode.endsWith("YouChoose")) {
|
||||||
chooser = source.getController();
|
chooser = sa.getActivatingPlayer();
|
||||||
} else if (mode.equals("RevealTgtChoose")) {
|
} else if (mode.equals("RevealTgtChoose")) {
|
||||||
chooser = firstTarget;
|
chooser = targets.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode.startsWith("Reveal")) {
|
if (mode.startsWith("Reveal")) {
|
||||||
@@ -260,7 +256,6 @@ public class DiscardEffect extends SpellAbilityEffect {
|
|||||||
p.getController().reveal(toBeDiscarded, ZoneType.Hand, p, Localizer.getInstance().getMessage("lblPlayerHasChosenCardsFrom", chooser.getName()));
|
p.getController().reveal(toBeDiscarded, ZoneType.Hand, p, Localizer.getInstance().getMessage("lblPlayerHasChosenCardsFrom", chooser.getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
discardedMap.put(p, toBeDiscarded);
|
discardedMap.put(p, toBeDiscarded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user