mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 03:38:01 +00:00
Compare commits
887 Commits
rememberSp
...
forge-1.6.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b39be5fe9 | ||
|
|
f6b38bf9ee | ||
|
|
af7cb0ae30 | ||
|
|
38b183b795 | ||
|
|
06afb22fd7 | ||
|
|
04cd2e8c45 | ||
|
|
6cf73e2a5d | ||
|
|
28ee9eb58d | ||
|
|
6a755db00d | ||
|
|
2bd008328a | ||
|
|
9feb583413 | ||
|
|
6ee9f14e59 | ||
|
|
2cf8ea8ae9 | ||
|
|
687972782a | ||
|
|
c67ea9cb2b | ||
|
|
1fec85b164 | ||
|
|
1bc3f9fc0e | ||
|
|
743711634e | ||
|
|
5e28d2c975 | ||
|
|
9c578fb57c | ||
|
|
e7fbe25af2 | ||
|
|
a52b39f8d3 | ||
|
|
07093df56d | ||
|
|
49931297ac | ||
|
|
15f3034ac0 | ||
|
|
d36651a779 | ||
|
|
245a95b336 | ||
|
|
22e2a42807 | ||
|
|
1c1d91c149 | ||
|
|
a8a2d7bbeb | ||
|
|
02064e05df | ||
|
|
c00add6050 | ||
|
|
04dfdac71c | ||
|
|
8d4566af3e | ||
|
|
3cc3ef0b13 | ||
|
|
f5a4360339 | ||
|
|
cb8ad8221e | ||
|
|
a050f7c26c | ||
|
|
0a709a6cf4 | ||
|
|
a9124743ef | ||
|
|
42f6cdbb46 | ||
|
|
17e7883996 | ||
|
|
91a8d1761a | ||
|
|
9c39d159e9 | ||
|
|
59abbfd51f | ||
|
|
953de7d6f1 | ||
|
|
51e9e2b692 | ||
|
|
9447561d53 | ||
|
|
928f903028 | ||
|
|
bc4826ed77 | ||
|
|
88a674240d | ||
|
|
1d4b9d68ed | ||
|
|
c1dfbc04e4 | ||
|
|
fa80df585f | ||
|
|
2cfbf8df30 | ||
|
|
ad6e9acbe1 | ||
|
|
3f450e813d | ||
|
|
9c62a0e2ab | ||
|
|
5dc883e932 | ||
|
|
94ab23f09f | ||
|
|
286e5721f7 | ||
|
|
eb5899ac15 | ||
|
|
a1fca42b57 | ||
|
|
16314d5ebc | ||
|
|
d5fe891440 | ||
|
|
13d99e209b | ||
|
|
9946155c71 | ||
|
|
6a6ca3f403 | ||
|
|
1c0d95b930 | ||
|
|
271b940723 | ||
|
|
8eecccff0b | ||
|
|
18f32a1657 | ||
|
|
5cd5020595 | ||
|
|
5aee626661 | ||
|
|
f2dae53c11 | ||
|
|
da93a734fb | ||
|
|
25042c9814 | ||
|
|
a239dbfc01 | ||
|
|
1b1502af0d | ||
|
|
6ca034eab9 | ||
|
|
99456c7bbb | ||
|
|
c0d87aecaa | ||
|
|
1d691a5342 | ||
|
|
de719b9aa5 | ||
|
|
190a56181e | ||
|
|
ae772e99a7 | ||
|
|
e13ddd9666 | ||
|
|
7890420fb0 | ||
|
|
8d056be356 | ||
|
|
b594cf2324 | ||
|
|
f048b04c2b | ||
|
|
fd1e8c1e7e | ||
|
|
af0d4b0c48 | ||
|
|
dc917643b5 | ||
|
|
6f90d27f38 | ||
|
|
80851d515a | ||
|
|
6dfca97bbb | ||
|
|
2a10750fdb | ||
|
|
a1976653f8 | ||
|
|
3e2fa43abe | ||
|
|
e3ae076ab7 | ||
|
|
64e09020bd | ||
|
|
cd033aa896 | ||
|
|
0e2222f97c | ||
|
|
7629144be1 | ||
|
|
dece031e13 | ||
|
|
611813c141 | ||
|
|
fdc9aea650 | ||
|
|
ccba38d944 | ||
|
|
def94171af | ||
|
|
33e172ec37 | ||
|
|
4f20cc638d | ||
|
|
93873d9984 | ||
|
|
e99e389221 | ||
|
|
cc1f9763a7 | ||
|
|
255a07fa2e | ||
|
|
b23b12ba66 | ||
|
|
7ff9329b56 | ||
|
|
76e0d3d62f | ||
|
|
888ad500a0 | ||
|
|
4f11fdc54f | ||
|
|
6e9c35a978 | ||
|
|
f4b31082b8 | ||
|
|
9be9abbdae | ||
|
|
3b059f9879 | ||
|
|
1ac44351c6 | ||
|
|
a7cc01ba65 | ||
|
|
bc75331809 | ||
|
|
1ebde0d976 | ||
|
|
99a6ad7288 | ||
|
|
56c1a0e6d1 | ||
|
|
341e80361d | ||
|
|
d4206445c6 | ||
|
|
508ea4d3d5 | ||
|
|
751900b376 | ||
|
|
c43542cbe4 | ||
|
|
7a68cc871f | ||
|
|
53443855b1 | ||
|
|
1faea98708 | ||
|
|
13057386c9 | ||
|
|
b914422546 | ||
|
|
4240953950 | ||
|
|
a306611853 | ||
|
|
40acdd7f2d | ||
|
|
4c90e4c5fa | ||
|
|
8c096d6cd1 | ||
|
|
31e9eeca05 | ||
|
|
7afa975cd9 | ||
|
|
09d5050766 | ||
|
|
54b866c58e | ||
|
|
3d6ac9f432 | ||
|
|
f0320bd567 | ||
|
|
801a999986 | ||
|
|
178e4a771b | ||
|
|
1cf8ccb843 | ||
|
|
007df1986d | ||
|
|
ad50390e38 | ||
|
|
317634d0e7 | ||
|
|
fb356f88bc | ||
|
|
60275823fc | ||
|
|
f52d525b18 | ||
|
|
d10ce0b827 | ||
|
|
3a6b925afd | ||
|
|
6f65baab38 | ||
|
|
7d294280a9 | ||
|
|
d57100c888 | ||
|
|
9d5d19f5c3 | ||
|
|
b6fe691a75 | ||
|
|
39b534ce2b | ||
|
|
15b95ffc79 | ||
|
|
c86307d44e | ||
|
|
4567c287d5 | ||
|
|
15032d7372 | ||
|
|
7af24d52fd | ||
|
|
fac6e7b689 | ||
|
|
ea33430706 | ||
|
|
5a5c7f96cb | ||
|
|
cbe8407b28 | ||
|
|
724f1ce10a | ||
|
|
e1ccedf881 | ||
|
|
cb7af23c13 | ||
|
|
e2c4c52bd7 | ||
|
|
305d7b69aa | ||
|
|
bb10e6e604 | ||
|
|
6ca9bd2926 | ||
|
|
29102dcab3 | ||
|
|
73f8fa1ac8 | ||
|
|
3f75adcfae | ||
|
|
1707e34860 | ||
|
|
a43019fcee | ||
|
|
812a5c873f | ||
|
|
73a4aa7461 | ||
|
|
6a1dc16b52 | ||
|
|
409499d9d9 | ||
|
|
947ebb0f0b | ||
|
|
7baab5786b | ||
|
|
1dfd9ac340 | ||
|
|
c4fad2f2fa | ||
|
|
ddcf0283a9 | ||
|
|
c0c2317adb | ||
|
|
f87107751f | ||
|
|
a22fd58fbc | ||
|
|
85b904ca65 | ||
|
|
3258d9a5fa | ||
|
|
846a30454d | ||
|
|
fbda01ef43 | ||
|
|
586e502780 | ||
|
|
688f8599be | ||
|
|
c161cdcf51 | ||
|
|
2937041c63 | ||
|
|
eafa4418d6 | ||
|
|
2ff19e05a1 | ||
|
|
e23acfb5de | ||
|
|
255c30ce9c | ||
|
|
49ba10d97b | ||
|
|
c3346c737e | ||
|
|
db3c4d2d2f | ||
|
|
c0c38dbef9 | ||
|
|
5fb20c7eb9 | ||
|
|
1791d96a78 | ||
|
|
89df7694d9 | ||
|
|
c2690a80e2 | ||
|
|
b50779177e | ||
|
|
b5fb772893 | ||
|
|
341d882a93 | ||
|
|
1ea8519be8 | ||
|
|
02abe851c0 | ||
|
|
c73ed59a52 | ||
|
|
10b68ae716 | ||
|
|
e99a507744 | ||
|
|
393207c9d8 | ||
|
|
facb85f74f | ||
|
|
c0103815be | ||
|
|
f7d40687b6 | ||
|
|
a24a844078 | ||
|
|
a820ba35f7 | ||
|
|
e4bd18e25c | ||
|
|
efd9b6a0fc | ||
|
|
baa4f8cd74 | ||
|
|
9067d676eb | ||
|
|
4b6cf610a7 | ||
|
|
12ee419158 | ||
|
|
53075e5099 | ||
|
|
33ee5a0a17 | ||
|
|
0bd29eb1e7 | ||
|
|
c4d859cb03 | ||
|
|
0cbd22ae69 | ||
|
|
05ecffcbd1 | ||
|
|
3f15be2732 | ||
|
|
cc13020b4b | ||
|
|
1a27142f1f | ||
|
|
89348173a5 | ||
|
|
8a9c27f9ae | ||
|
|
7da2316c0a | ||
|
|
6807257630 | ||
|
|
223136dd6f | ||
|
|
0b997a2937 | ||
|
|
66debd6806 | ||
|
|
063ebf2f70 | ||
|
|
940cd71d34 | ||
|
|
fc987a81a0 | ||
|
|
888f5953b1 | ||
|
|
c30086390d | ||
|
|
37207559e6 | ||
|
|
93bd90f139 | ||
|
|
e67b55d3cf | ||
|
|
df55bc5d34 | ||
|
|
ce7b66652c | ||
|
|
c091eefc84 | ||
|
|
b7508b8c3a | ||
|
|
b5a0e15327 | ||
|
|
d29ed9a5fe | ||
|
|
62cf8533e0 | ||
|
|
7a96ff2d4b | ||
|
|
cafd7d9c64 | ||
|
|
5377376585 | ||
|
|
69661cab63 | ||
|
|
15520fcc67 | ||
|
|
eedbdeca99 | ||
|
|
34380befaf | ||
|
|
da5b1a18bf | ||
|
|
a546339ea2 | ||
|
|
558442a6bb | ||
|
|
16c9cc7eac | ||
|
|
71da9ca610 | ||
|
|
d54ca72994 | ||
|
|
79ce24b682 | ||
|
|
0662dc5964 | ||
|
|
e7f7e62cbf | ||
|
|
17c280f052 | ||
|
|
8d3fe2088a | ||
|
|
a87adfaf16 | ||
|
|
ed895a5248 | ||
|
|
143a254189 | ||
|
|
25a3367d74 | ||
|
|
400c7d366b | ||
|
|
43cdbbdbee | ||
|
|
bddba4c6dc | ||
|
|
4ab79ce75d | ||
|
|
42eba01a76 | ||
|
|
f0efc2089c | ||
|
|
3095c25f6d | ||
|
|
f38ec8d4a0 | ||
|
|
56b3f04120 | ||
|
|
e35978594a | ||
|
|
99aab2e716 | ||
|
|
17df25399f | ||
|
|
405e07a78e | ||
|
|
b090cdfa41 | ||
|
|
8c810a120e | ||
|
|
fe4ce14a4b | ||
|
|
ae8271de01 | ||
|
|
22aa98bf92 | ||
|
|
f5f164af93 | ||
|
|
48216f95e1 | ||
|
|
6ef9f4bc7b | ||
|
|
4ddf811118 | ||
|
|
3cf8b956e4 | ||
|
|
9bf908ccb4 | ||
|
|
38d3f76bb5 | ||
|
|
0c2cfdce34 | ||
|
|
ea2b61cf5b | ||
|
|
ffda74101f | ||
|
|
08f47d58a1 | ||
|
|
f87f6bdebe | ||
|
|
2e70ab329e | ||
|
|
73749f28da | ||
|
|
f7134f0a54 | ||
|
|
231b2da573 | ||
|
|
bf5c319bfe | ||
|
|
d781b6ba61 | ||
|
|
98282acc60 | ||
|
|
d2917f0e43 | ||
|
|
919aecd9a1 | ||
|
|
3c17b22a95 | ||
|
|
da9373a0df | ||
|
|
3631b8be75 | ||
|
|
63ec3aae87 | ||
|
|
46670d50f3 | ||
|
|
1416b668a3 | ||
|
|
893cc3b08f | ||
|
|
25858f625b | ||
|
|
9e6d9e8e30 | ||
|
|
1244ac89fd | ||
|
|
facf22c092 | ||
|
|
d1895ef97e | ||
|
|
0575cd98d9 | ||
|
|
2f466834f9 | ||
|
|
3282ccf6b1 | ||
|
|
6ae7cddcb9 | ||
|
|
0d79a84b9f | ||
|
|
6fcb996938 | ||
|
|
27651313a7 | ||
|
|
9d762b5740 | ||
|
|
e51afbc21c | ||
|
|
0ca3378901 | ||
|
|
b544718140 | ||
|
|
dc4b65593e | ||
|
|
5f5b34478d | ||
|
|
64e7aa902b | ||
|
|
f6f36b75d9 | ||
|
|
99b761082e | ||
|
|
29d917d8f5 | ||
|
|
63abdd6d00 | ||
|
|
e4d41001db | ||
|
|
4ed17e1e16 | ||
|
|
a70136c98c | ||
|
|
e42f3f3369 | ||
|
|
6fc53b8fe4 | ||
|
|
557be177d8 | ||
|
|
3fab25ab8c | ||
|
|
3be4c7e379 | ||
|
|
452a47e573 | ||
|
|
d002ddaa5d | ||
|
|
95a69f053b | ||
|
|
f2c48a386b | ||
|
|
7db1b4a106 | ||
|
|
8b186bb890 | ||
|
|
2b0fc5a00a | ||
|
|
f63e59fbda | ||
|
|
cd044f1532 | ||
|
|
7f16a3dce2 | ||
|
|
3d3f1b08e4 | ||
|
|
f4c326826e | ||
|
|
bf0e0a94e4 | ||
|
|
3a71e2e948 | ||
|
|
a739492591 | ||
|
|
7a14109abc | ||
|
|
708886e8cf | ||
|
|
ab56e1f7d4 | ||
|
|
038bc27074 | ||
|
|
4cc397baa4 | ||
|
|
398b2ee324 | ||
|
|
6f85b09ee4 | ||
|
|
00f6445049 | ||
|
|
24d857f269 | ||
|
|
cb13f0a685 | ||
|
|
e8f14d5a6c | ||
|
|
64c9f7ffb3 | ||
|
|
899532666e | ||
|
|
d76cd0e37d | ||
|
|
749dc79775 | ||
|
|
eb17f4972a | ||
|
|
c9f9fd93cd | ||
|
|
e83c78e2b4 | ||
|
|
bf7f07b432 | ||
|
|
245994d094 | ||
|
|
c055e4d89c | ||
|
|
fcdf49bafd | ||
|
|
b56472555a | ||
|
|
06df7cb036 | ||
|
|
d6b011fd72 | ||
|
|
b853f34c9e | ||
|
|
4da05f03dc | ||
|
|
52cc02a316 | ||
|
|
d188708028 | ||
|
|
1e39137ab5 | ||
|
|
47c89fb0ae | ||
|
|
cf57832970 | ||
|
|
6c1487a491 | ||
|
|
6f26becf5c | ||
|
|
0a543ba35e | ||
|
|
09f90bfe35 | ||
|
|
0c06346ca0 | ||
|
|
1df44f8166 | ||
|
|
0d67c0fb9e | ||
|
|
5885c37a0f | ||
|
|
189514f37c | ||
|
|
ee8a45b012 | ||
|
|
983eaaff3c | ||
|
|
9d79959b87 | ||
|
|
a3d75035b9 | ||
|
|
941adbbc68 | ||
|
|
67167c29fc | ||
|
|
d763e3a1db | ||
|
|
ebddcb3210 | ||
|
|
526104104c | ||
|
|
ebd5e7b419 | ||
|
|
d491972995 | ||
|
|
60c68b10d9 | ||
|
|
471fcaa938 | ||
|
|
7111c3f3aa | ||
|
|
384b5c2c5b | ||
|
|
84a2fed516 | ||
|
|
a1c137a86b | ||
|
|
87359257fd | ||
|
|
4e8c18b232 | ||
|
|
95f46dfde3 | ||
|
|
20c978a64f | ||
|
|
bb57cb1e1b | ||
|
|
29b0c6eb69 | ||
|
|
76b929b814 | ||
|
|
fbde2483e4 | ||
|
|
55ccd88790 | ||
|
|
5b9e121674 | ||
|
|
d86e04222d | ||
|
|
5b10cb0e26 | ||
|
|
5d99a0e918 | ||
|
|
8385e80fd3 | ||
|
|
c7d8a112d0 | ||
|
|
6a715bdab7 | ||
|
|
2284964c88 | ||
|
|
bd83fc675c | ||
|
|
7ea81fce8b | ||
|
|
519b05fcc1 | ||
|
|
8ab3a00a3a | ||
|
|
544327330d | ||
|
|
c3e895936e | ||
|
|
d223089c05 | ||
|
|
f5725766c0 | ||
|
|
585f0299b2 | ||
|
|
3a8c07d9c1 | ||
|
|
30109aaeba | ||
|
|
c02f4cf081 | ||
|
|
2c0ec81812 | ||
|
|
2d8286adf1 | ||
|
|
95cb17849e | ||
|
|
e42608f856 | ||
|
|
cdb47e0e10 | ||
|
|
b008a75043 | ||
|
|
81d3ab86f9 | ||
|
|
efe71e8384 | ||
|
|
cb494280a9 | ||
|
|
ba587ea702 | ||
|
|
4b00795f3d | ||
|
|
faca59beb0 | ||
|
|
41593d0060 | ||
|
|
ce865da924 | ||
|
|
1300194e3a | ||
|
|
903c708e1f | ||
|
|
b5d97dc256 | ||
|
|
33c398fcb6 | ||
|
|
3fc37c735e | ||
|
|
1b44c953fc | ||
|
|
816d136350 | ||
|
|
cd1bfc6b08 | ||
|
|
e72879d173 | ||
|
|
d18ef04536 | ||
|
|
426c0ca4c8 | ||
|
|
7b521919fb | ||
|
|
5b547d509b | ||
|
|
98830dde13 | ||
|
|
1a5fa823e5 | ||
|
|
c769aa07dd | ||
|
|
10745eb9c7 | ||
|
|
18934c7f9f | ||
|
|
c54682850b | ||
|
|
f241a62d95 | ||
|
|
31bca6c614 | ||
|
|
567aab0c43 | ||
|
|
ef1f1c33cf | ||
|
|
a18d98c231 | ||
|
|
66b97d5d8e | ||
|
|
3c83b62d98 | ||
|
|
0569a0b36d | ||
|
|
a8abbadec4 | ||
|
|
6235b42c31 | ||
|
|
1d41f1b330 | ||
|
|
ad8778d397 | ||
|
|
e38bd51193 | ||
|
|
92ba90bc9e | ||
|
|
e5584ea065 | ||
|
|
17dedb4bf4 | ||
|
|
eb3fb9e1ce | ||
|
|
b27f49c4ff | ||
|
|
8d4854bd5d | ||
|
|
1c33e3b039 | ||
|
|
eda0a49097 | ||
|
|
ea8c6630e0 | ||
|
|
0d4310382e | ||
|
|
4ae1547384 | ||
|
|
4192195579 | ||
|
|
0d11e28c11 | ||
|
|
9cf78d0eb4 | ||
|
|
7843004c40 | ||
|
|
4c7c2ee1b5 | ||
|
|
05538bfba2 | ||
|
|
6fe6d6ecfd | ||
|
|
34a8184cc7 | ||
|
|
5d9f86bf20 | ||
|
|
661555551d | ||
|
|
02e118b2e2 | ||
|
|
b59fbe43f0 | ||
|
|
ac51d4170f | ||
|
|
dc91df0e19 | ||
|
|
5bce464268 | ||
|
|
60d5afe9a2 | ||
|
|
ef6e09d4c4 | ||
|
|
e330cabc64 | ||
|
|
76324bc6db | ||
|
|
30607291fc | ||
|
|
bee8348b67 | ||
|
|
51fb900eb5 | ||
|
|
8b902671d6 | ||
|
|
7dbbf0a554 | ||
|
|
e75b15b7cb | ||
|
|
b2063433c1 | ||
|
|
43cca9b635 | ||
|
|
b29f390b62 | ||
|
|
f83a97bedd | ||
|
|
c58690dd96 | ||
|
|
3402cddd1d | ||
|
|
3956a5b558 | ||
|
|
72b60c91f5 | ||
|
|
d063e98e33 | ||
|
|
8743cba5d7 | ||
|
|
1d2bbe4923 | ||
|
|
58311e5265 | ||
|
|
8c020c7c8c | ||
|
|
ba25696ea7 | ||
|
|
68359097df | ||
|
|
4b882ad215 | ||
|
|
ee6806b36a | ||
|
|
343723e529 | ||
|
|
15bf426386 | ||
|
|
77184dc930 | ||
|
|
5f11897d11 | ||
|
|
40764691fd | ||
|
|
210cc6164d | ||
|
|
11f597bb23 | ||
|
|
015623a122 | ||
|
|
b04e16d1ca | ||
|
|
e3f24dccd4 | ||
|
|
2e0812dd6a | ||
|
|
9deeec7b14 | ||
|
|
b58b2b93e2 | ||
|
|
2697f5f404 | ||
|
|
78db487e8d | ||
|
|
1ecd1a8340 | ||
|
|
c7539de77d | ||
|
|
b0ee2cedff | ||
|
|
7a0453390a | ||
|
|
1b47a2df87 | ||
|
|
fd54606277 | ||
|
|
25b02141bd | ||
|
|
bcc4158956 | ||
|
|
fc5b76fbb4 | ||
|
|
38ddca0acd | ||
|
|
7ed935c43c | ||
|
|
14629715a2 | ||
|
|
e71ff26b81 | ||
|
|
7d6dec773a | ||
|
|
e52bcc6cdc | ||
|
|
b3e1d96aff | ||
|
|
e13880c87e | ||
|
|
0b14efebb8 | ||
|
|
18a3bfe0b8 | ||
|
|
d8da15d8ae | ||
|
|
5bd9f324e0 | ||
|
|
e0b463c6ea | ||
|
|
01742f06f6 | ||
|
|
dbe212065d | ||
|
|
4b8584376d | ||
|
|
6cf7d3ae80 | ||
|
|
104aaa1255 | ||
|
|
1ba1226661 | ||
|
|
f8bf8c7f28 | ||
|
|
763cb3d08e | ||
|
|
5e94c6c8d3 | ||
|
|
b08ce49057 | ||
|
|
7c98ed75be | ||
|
|
1c9a515bb3 | ||
|
|
a462d34249 | ||
|
|
e1ceed159f | ||
|
|
89757b9677 | ||
|
|
01c52667ad | ||
|
|
25003aa74e | ||
|
|
766277673d | ||
|
|
a1ad2208c8 | ||
|
|
0bfdfb78d3 | ||
|
|
b23425ea9a | ||
|
|
8b7ace6d1c | ||
|
|
1dc8ef5244 | ||
|
|
625585f005 | ||
|
|
acdee9ff0c | ||
|
|
54c5d3f344 | ||
|
|
4fa8fa3993 | ||
|
|
3b21b2a610 | ||
|
|
07324f63d9 | ||
|
|
79c3570bcc | ||
|
|
d9400fe069 | ||
|
|
70f0f1108b | ||
|
|
62316abf38 | ||
|
|
e2ddcdda4d | ||
|
|
e604d9d0cb | ||
|
|
ed218b6e4c | ||
|
|
866c959036 | ||
|
|
b2616860c0 | ||
|
|
0ceb882ab0 | ||
|
|
560dc572c2 | ||
|
|
e891e0d31d | ||
|
|
3c1b106837 | ||
|
|
eefa4e46d1 | ||
|
|
581ef20405 | ||
|
|
27bae4a72b | ||
|
|
6686937742 | ||
|
|
d6a466df11 | ||
|
|
7eedd04aa1 | ||
|
|
e3c9a40a17 | ||
|
|
76ebfa9460 | ||
|
|
6cdae2e5ff | ||
|
|
e0fd812590 | ||
|
|
2c2104dfe6 | ||
|
|
4661a19982 | ||
|
|
9778f9e3a0 | ||
|
|
b6019c6308 | ||
|
|
7006c548a5 | ||
|
|
2eae26cefb | ||
|
|
ccd1117762 | ||
|
|
1917871b98 | ||
|
|
96e6111400 | ||
|
|
f8d360f25f | ||
|
|
5901c6b66b | ||
|
|
b9782f9790 | ||
|
|
74c69f0d37 | ||
|
|
5a81cdc40d | ||
|
|
7ebe97f802 | ||
|
|
956ab8e96e | ||
|
|
b821ec7af5 | ||
|
|
6a92f68602 | ||
|
|
d0c5bcf053 | ||
|
|
4069b603c2 | ||
|
|
0813f9bc66 | ||
|
|
0687fc2a31 | ||
|
|
3f52a8c4f5 | ||
|
|
82e276c661 | ||
|
|
195fe58dcb | ||
|
|
afd1070850 | ||
|
|
8580d585df | ||
|
|
090d6ad9fb | ||
|
|
8ed1ad4afc | ||
|
|
bc854f4d42 | ||
|
|
d8585fd925 | ||
|
|
5ae5fef43e | ||
|
|
48a0ac6039 | ||
|
|
57a3462623 | ||
|
|
929b5fbcfe | ||
|
|
56fda56807 | ||
|
|
872defd992 | ||
|
|
e78202e432 | ||
|
|
657d85530a | ||
|
|
a6e0f9b472 | ||
|
|
86abb0d1d5 | ||
|
|
28a5bd109e | ||
|
|
b357096da2 | ||
|
|
d1d1df27bc | ||
|
|
d7ebafe883 | ||
|
|
f09130eb86 | ||
|
|
50e6a06478 | ||
|
|
65fc4a4371 | ||
|
|
84ef513489 | ||
|
|
67ca319674 | ||
|
|
1f4142e368 | ||
|
|
29fd940a3d | ||
|
|
480792966b | ||
|
|
b8147a2e58 | ||
|
|
2b8a756932 | ||
|
|
f78bfce802 | ||
|
|
ebcb4e28de | ||
|
|
f0c45cf814 | ||
|
|
aff8d5ce01 | ||
|
|
94064a2a13 | ||
|
|
6359021370 | ||
|
|
6090bc8117 | ||
|
|
8bf2bd6ddc | ||
|
|
264744645e | ||
|
|
ab1e1d2386 | ||
|
|
87ad257c46 | ||
|
|
bc5e7f07ae | ||
|
|
f18651be7a | ||
|
|
9921f58155 | ||
|
|
3ade967f85 | ||
|
|
243c90ced9 | ||
|
|
c1c421fff0 | ||
|
|
bf6f8048d6 | ||
|
|
f26935f37f | ||
|
|
f3f7700ba9 | ||
|
|
2817c4ef61 | ||
|
|
166700b573 | ||
|
|
6edb5ad591 | ||
|
|
66f2ab4e91 | ||
|
|
1f211e6ed5 | ||
|
|
41093c2f7f | ||
|
|
f4df89dca8 | ||
|
|
ffabf14192 | ||
|
|
5ce9db0b08 | ||
|
|
5165c16233 | ||
|
|
1e34fd27fe | ||
|
|
12b464dbcb | ||
|
|
76e3f0305b | ||
|
|
bb57003518 | ||
|
|
dfbfcf0a11 | ||
|
|
c4c061d669 | ||
|
|
e76db8e7ff | ||
|
|
9c4fd8b570 | ||
|
|
5d2e7a16d3 | ||
|
|
cf3a28b120 | ||
|
|
10f8616e37 | ||
|
|
1bade895e1 | ||
|
|
1940bca932 | ||
|
|
620c27a117 | ||
|
|
3071f1f74b | ||
|
|
07c2065917 | ||
|
|
0f44b644c1 | ||
|
|
427b2973b8 | ||
|
|
25d1d2bb20 | ||
|
|
8f2123f183 | ||
|
|
595fbae34f | ||
|
|
3fd842c1e4 | ||
|
|
11f9520c1e | ||
|
|
40c64a943a | ||
|
|
4f953bf2e1 | ||
|
|
c7d9646f7f | ||
|
|
d4b4dc5ba0 | ||
|
|
2cd86bfe1e | ||
|
|
2f9fb96d29 | ||
|
|
39204513fb | ||
|
|
ebaa96004f | ||
|
|
d381251472 | ||
|
|
f00e759a93 | ||
|
|
aab63e2029 | ||
|
|
3749f2a5de | ||
|
|
aa7ad578d5 | ||
|
|
fb80dece04 | ||
|
|
482ab4b87c | ||
|
|
f3f9a915d5 | ||
|
|
52184e24ce | ||
|
|
b6dbfcee96 | ||
|
|
e07be68786 | ||
|
|
9c3ae840b8 | ||
|
|
20eba0fbd7 | ||
|
|
735516e6d5 | ||
|
|
087495f5a0 | ||
|
|
b94ec24948 | ||
|
|
e3257e025d | ||
|
|
2640a509f9 | ||
|
|
e2eb957af9 | ||
|
|
81f07cb8a3 | ||
|
|
1e3cb2e66c | ||
|
|
76da5b0cac | ||
|
|
fdc1c32287 | ||
|
|
3b38547fc9 | ||
|
|
53d1716255 | ||
|
|
0606a00942 | ||
|
|
af3c645521 | ||
|
|
e5093c6d2f | ||
|
|
59102b0e08 | ||
|
|
17cff99c2b | ||
|
|
7d0dbff8bc | ||
|
|
6974e2de27 | ||
|
|
770ed4524a | ||
|
|
7f4dc85554 | ||
|
|
005fa3d732 | ||
|
|
bbe8d79400 | ||
|
|
edaab9d7de | ||
|
|
842c6e681d | ||
|
|
05be8406cc | ||
|
|
7aa879fb8e | ||
|
|
1e6a9b8c26 | ||
|
|
b2e9c88f62 | ||
|
|
3609ff9eff | ||
|
|
09fc3ae60c | ||
|
|
948c13dd15 | ||
|
|
b8f7d08fae | ||
|
|
576b7e2dd3 | ||
|
|
024b2e1a01 | ||
|
|
daf4b9d974 | ||
|
|
36b3431975 | ||
|
|
3ded3926f4 | ||
|
|
69502dd97b | ||
|
|
234304f9ec | ||
|
|
193a1cc255 | ||
|
|
d0b569a07f | ||
|
|
b17edd31ac | ||
|
|
891f61701c | ||
|
|
e35f5098b9 | ||
|
|
55da7435f8 | ||
|
|
f0a561b1ee | ||
|
|
bbd48d1033 | ||
|
|
847013c6c7 | ||
|
|
61aa144631 | ||
|
|
dd87f74eb4 | ||
|
|
95dbc5333e | ||
|
|
0d2114416c | ||
|
|
215808b011 | ||
|
|
1ced8bba7d | ||
|
|
468ff7fc50 | ||
|
|
0eed2c67d1 | ||
|
|
4168ef0b0d | ||
|
|
bf59d932f4 | ||
|
|
765b0e4625 | ||
|
|
16058bbea2 | ||
|
|
f47f95cd2e | ||
|
|
7f0f62abb6 | ||
|
|
22627b57f0 | ||
|
|
5a81cace02 | ||
|
|
3f172e6872 | ||
|
|
52dcdaa47d | ||
|
|
d4476cbd2b | ||
|
|
d2c4ab28c2 | ||
|
|
23d67ab0cf | ||
|
|
3f55b9f503 | ||
|
|
e222444809 | ||
|
|
83ed648966 | ||
|
|
a5c1d88460 | ||
|
|
fee83e0db4 | ||
|
|
dadf1b12d3 | ||
|
|
5ff8298b62 | ||
|
|
44e181981d | ||
|
|
52b659e902 | ||
|
|
fb8bfdc449 | ||
|
|
06c164a013 | ||
|
|
fe481d40e2 | ||
|
|
abc42d5a32 | ||
|
|
308f525803 | ||
|
|
f673397c77 | ||
|
|
cd6831eb2d | ||
|
|
dde09633d9 | ||
|
|
af94d5e9ce | ||
|
|
3d33192efa | ||
|
|
371e083a0e | ||
|
|
08bb85d58f | ||
|
|
a67c34d36d | ||
|
|
a8dd39c5b3 | ||
|
|
815f989310 | ||
|
|
2cda73ad77 | ||
|
|
bf290a84bd |
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -231,3 +231,6 @@ forge-gui/tools/oracleScript.log
|
||||
/release.properties
|
||||
/target
|
||||
/test-output
|
||||
.settings
|
||||
.classpath
|
||||
.project
|
||||
33
.gitlab/issue_templates/Bug.md
Normal file
33
.gitlab/issue_templates/Bug.md
Normal file
@@ -0,0 +1,33 @@
|
||||
Summary
|
||||
|
||||
(Summarize the bug encountered concisely)
|
||||
|
||||
|
||||
Steps to reproduce
|
||||
|
||||
(How one can reproduce the issue - this is very important. Specific cards and specific actions especially)
|
||||
|
||||
|
||||
Which version of Forge are you on (Release, Snapshot? Desktop, Android?)
|
||||
|
||||
|
||||
What is the current bug behavior?
|
||||
|
||||
(What actually happens)
|
||||
|
||||
|
||||
What is the expected correct behavior?
|
||||
|
||||
(What you should see instead)
|
||||
|
||||
|
||||
Relevant logs and/or screenshots
|
||||
|
||||
(Paste/Attach your game.log from the crash - please use code blocks (```)) Also, provide screenshots of the current state.
|
||||
|
||||
|
||||
Possible fixes
|
||||
|
||||
(If you can, link to the line of code that might be responsible for the problem)
|
||||
|
||||
/label ~needs-investigation
|
||||
15
.gitlab/issue_templates/Feature.md
Normal file
15
.gitlab/issue_templates/Feature.md
Normal file
@@ -0,0 +1,15 @@
|
||||
Summary
|
||||
|
||||
(Summarize the feature you wish concisely)
|
||||
|
||||
|
||||
Example screenshots
|
||||
|
||||
(If this is a UI change, please provide an example screenshot of how this feature might work)
|
||||
|
||||
|
||||
Feature type
|
||||
|
||||
(Where in Forge does this belong? e.g. Quest Mode, Deck Editor, Limited, Constructed, etc.)
|
||||
|
||||
/label ~feature request
|
||||
12
.project
12
.project
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>forge</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
@@ -1,7 +0,0 @@
|
||||
add_header=true
|
||||
add_todo=false
|
||||
eclipse.preferences.version=1
|
||||
header_text=/*\n * Forge\: Play Magic\: the Gathering.\n * Copyright (C) 2011 Forge Team\n *\n * This program is free software\: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n * \n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n * \n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http\://www.gnu.org/licenses/>.\n */
|
||||
project_specific_settings=true
|
||||
replacements=<?xml version\="1.0" standalone\="yes"?>\n\n<replacements>\n<replacement key\="get" scope\="1" mode\="0">Gets the</replacement>\n<replacement key\="set" scope\="1" mode\="0">Sets the</replacement>\n<replacement key\="add" scope\="1" mode\="0">Adds the</replacement>\n<replacement key\="edit" scope\="1" mode\="0">Edits the</replacement>\n<replacement key\="remove" scope\="1" mode\="0">Removes the</replacement>\n<replacement key\="init" scope\="1" mode\="0">Inits the</replacement>\n<replacement key\="parse" scope\="1" mode\="0">Parses the</replacement>\n<replacement key\="create" scope\="1" mode\="0">Creates the</replacement>\n<replacement key\="build" scope\="1" mode\="0">Builds the</replacement>\n<replacement key\="is" scope\="1" mode\="0">Checks if is</replacement>\n<replacement key\="print" scope\="1" mode\="0">Prints the</replacement>\n<replacement key\="has" scope\="1" mode\="0">Checks for</replacement>\n</replacements>\n\n
|
||||
visibility_private=false
|
||||
@@ -1,2 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
||||
@@ -1,284 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.source=1.7
|
||||
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
|
||||
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
|
||||
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
|
||||
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
|
||||
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
|
||||
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
|
||||
org.eclipse.jdt.core.formatter.comment.format_header=false
|
||||
org.eclipse.jdt.core.formatter.comment.format_html=true
|
||||
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
|
||||
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
|
||||
org.eclipse.jdt.core.formatter.comment.format_source_code=true
|
||||
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
|
||||
org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
|
||||
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
|
||||
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
|
||||
org.eclipse.jdt.core.formatter.comment.line_length=80
|
||||
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
|
||||
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
|
||||
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
|
||||
org.eclipse.jdt.core.formatter.compact_else_if=true
|
||||
org.eclipse.jdt.core.formatter.continuation_indentation=2
|
||||
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
|
||||
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
|
||||
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
|
||||
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
|
||||
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
|
||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
|
||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
|
||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
|
||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
|
||||
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
|
||||
org.eclipse.jdt.core.formatter.indent_empty_lines=false
|
||||
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
|
||||
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
|
||||
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
|
||||
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
|
||||
org.eclipse.jdt.core.formatter.indentation.size=4
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
|
||||
org.eclipse.jdt.core.formatter.join_lines_in_comments=true
|
||||
org.eclipse.jdt.core.formatter.join_wrapped_lines=true
|
||||
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
|
||||
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
|
||||
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
|
||||
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
|
||||
org.eclipse.jdt.core.formatter.lineSplit=120
|
||||
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
|
||||
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
|
||||
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
|
||||
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
|
||||
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
|
||||
org.eclipse.jdt.core.formatter.tabulation.char=space
|
||||
org.eclipse.jdt.core.formatter.tabulation.size=4
|
||||
org.eclipse.jdt.core.formatter.use_on_off_tags=false
|
||||
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true
|
||||
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
|
||||
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
|
||||
File diff suppressed because one or more lines are too long
@@ -1,4 +0,0 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
||||
230
README.md
Normal file
230
README.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# Forge
|
||||
|
||||
Gitlab repo is found [here](https://git.cardforge.org/core-developers/forge).
|
||||
|
||||
Dev instructions here: [Getting Started](https://www.slightlymagic.net/wiki/Forge:How_to_Get_Started_Developing_Forge) (Somewhat outdated)
|
||||
|
||||
Discord channel [here](https://discordapp.com/channels/267367946135928833/267742313390931968)
|
||||
|
||||
# Requirements / Tools
|
||||
|
||||
- Java IDE such as IntelliJ or Eclipse
|
||||
- Java JDK 8 or later
|
||||
- Git
|
||||
- Git client (optional)
|
||||
- Maven
|
||||
- Gitlab account
|
||||
- Libgdx (optional: familiarity with this library is helpful for mobile platform development)
|
||||
- Android SDK (optional: for Android releases)
|
||||
- RoboVM (optional: for iOS releases) (TBD: Current status of support by libgdx)
|
||||
|
||||
# Project Quick Setup
|
||||
|
||||
- Log in to gitlab with your user account and fork the project.
|
||||
|
||||
- Clone your forked project to your local machine
|
||||
|
||||
- Go to the project location on your machine. Run Maven to download all dependencies and build a snapshot. Example for Windows & Linux: `mvn -U -B clean -P windows-linux install`
|
||||
|
||||
# Eclipse
|
||||
|
||||
Eclipse includes Maven integration so a separate install is not necessary. For other IDEs, your mileage may vary.
|
||||
|
||||
## Project Setup
|
||||
|
||||
- Follow the instructions for cloning from Gitlab. You'll need a Gitlab account setup and an SSH key defined.
|
||||
|
||||
If you are on a Windows machine you can use Putty with TortoiseGit for SSH keys. Run puttygen.exe to generate the key -- save the private key and export
|
||||
the OpenSSH public key. If you just leave the dialog open, you can copy and paste the key from it to your Gitlab profile under
|
||||
"SSH keys". Run pageant.exe and add the private key generated earlier. TortoiseGit will use this for accessing Gitlab.
|
||||
|
||||
- Fork the Forge git repo to your Gitlab account.
|
||||
|
||||
- Clone your forked repo to your local machine.
|
||||
|
||||
- Make sure the Java SDK is installed -- not just the JRE. Java 8 or newer required. If you execute `java -version` at the shell or command prompt, it should report version 1.8 or later.
|
||||
|
||||
- Install Eclipse 2018-12 or later for Java. Launch it.
|
||||
|
||||
- Create a workspace. Go to the workbench. Right-click inside of Package Explorer > Import... > Maven > Existing Maven Projects > Navigate to root path of the local forge repo and
|
||||
ensure everything is checked > Finish.
|
||||
|
||||
- Let Eclipse run through building the project. You may be prompted for resolving any missing Maven plugins -- accept the ones offered. You may see errors appear in the "Problems" tab. These should
|
||||
be automatically resolved as plug-ins are installed and Eclipse continues the build process. If this is the first time for some plug-in installs, Eclipse may prompt you to restart. Do so. Be patient
|
||||
for this first time through.
|
||||
|
||||
- Once everything builds, all errors should disappear. You can now advance to Project launch.
|
||||
|
||||
## Project Launch
|
||||
|
||||
### Desktop
|
||||
|
||||
This is the standard configuration used for releasing to Windows / Linux / MacOS.
|
||||
|
||||
- Right-click on forge-gui-desktop > Run As... > Java Application > "Main - forge.view" > Ok
|
||||
|
||||
- The familiar Forge splash screen, etc. should appear. Enjoy!
|
||||
|
||||
### Mobile (Desktop dev)
|
||||
|
||||
This is the configuration used for doing mobile development using the Windows / Linux / MacOS front-end. Knowledge of libgdx is helpful here.
|
||||
|
||||
- Right-click on forge-gui-mobile-dev > Run As... > Java Application > "Main - forge.app" > Ok.
|
||||
|
||||
- A view similar to a mobile phone should appear. Enjoy!
|
||||
|
||||
## Eclipse / Android SDK Integration
|
||||
|
||||
Google no longer supports Android SDK releases for Eclipse. That said, it is still possible to build and debug Android platforms.
|
||||
|
||||
### Android SDK
|
||||
|
||||
Reference SO for obtaining a specific release: https://stackoverflow.com/questions/27043522/where-can-i-download-an-older-version-of-the-android-sdk
|
||||
|
||||
#### Windows
|
||||
|
||||
Download the following archived version of the Android SDK: http://dl-ssl.google.com/android/repository/tools_r25.2.3-windows.zip. Install it somewhere on your machine. This is referenced
|
||||
in the following instructions as your 'Android SDK Install' path.
|
||||
|
||||
#### Linux / Mac OSX
|
||||
|
||||
TBD
|
||||
|
||||
### Android Plugin for Eclipse
|
||||
|
||||
Google's last plugin release does not work completely with target's running Android 7.0 or later. Download the ADT-24.2.0-20160729.zip plugin
|
||||
from: https://github.com/khaledev/ADT/releases
|
||||
|
||||
In Eclipse go to: Help > Install New Software... > Add > Name: ADT Update, Click on the "Archive:" button and navigate to the downloaded ADT-24.2.0-20160729.zip file > Add. Install all "Developer Tools". Eclipse
|
||||
should restart and prompt you to run the SDK Manager. Launch it and continue to the next steps below.
|
||||
|
||||
### Android Platform
|
||||
|
||||
In Eclipse, if the SDK Manager is not already running, go to Window > Android SDK Manager. Install the following options / versions:
|
||||
|
||||
- Android SDK Build-tools 26.0.1
|
||||
- Android 7.1.1 (API 25) SDK Platform
|
||||
- Google USB Driver 11
|
||||
|
||||
Note that this will populate additional tools in the Android SDK install path extracted above.
|
||||
|
||||
### Proguard update
|
||||
|
||||
The Proguard included with the Android SDK Build-tools is outdated and does not work with Java 1.8. Download Proguard 6.0.3 from https://sourceforge.net/projects/proguard/files/proguard/6.0/.
|
||||
|
||||
- Go to the Android SDK install path. Rename the tools/proguard/ path to tools/proguard4.7/.
|
||||
|
||||
- Extract Proguard 6.0.3 to the Android SDK install path under tools/. You will need to rename the dir proguard6.0.3/ to proguard/.
|
||||
|
||||
### Android Build
|
||||
|
||||
The Eclipse plug-ins do NOT support building things for Android. They do however allow you to use the debugger so you can still set breakpoints and trace
|
||||
things out. The steps below show how to generate a debug Android 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
|
||||
|
||||
2) Run forge Maven build. If everything built, you should see "BUILD SUCCESS" in the Console View.
|
||||
|
||||
3) Right-click on the forge-gui-android project. Run as.. > Maven build...
|
||||
|
||||
- On the Main tab, set Goals: install, Profiles: android-debug
|
||||
- On the Environment tab, you may need to define the variable ANDROID_HOME with the value containing the path to your Android SDK installation. For example, Variable: ANDROID_HOME, Value: Your Android SDK install path here.
|
||||
|
||||
4) Run the forge-gui-android Maven build. This may take a few minutes. If everything worked, you should see "BUILD SUCCESS" in the Console View.
|
||||
|
||||
Assuming you got this far, you should have an Android forge-android-[version].apk in the forge-gui-android/target path.
|
||||
|
||||
### Android Deploy
|
||||
|
||||
You'll need to have the Android SDK install path platform-tools/ path in your command search path to easily deploy builds.
|
||||
|
||||
- Open a command prompt. Navigate to the forge-gui-android/target/ path.
|
||||
|
||||
- Connect your Android device to your dev machine.
|
||||
|
||||
- Ensure the device is visible using `adb devices`
|
||||
|
||||
- Remove the old Forge install if present: `adb uninstall forge.app`
|
||||
|
||||
- Install the new apk: `adb install forge-android-[version].apk`
|
||||
|
||||
### Android Debugging
|
||||
|
||||
Assuming the apk is installed, launch it from the device.
|
||||
|
||||
In Eclipse, launch the DDMS. Window > Perspective > Open Perspective > Other... > DDMS. You should see the forge app in the list. Highlight the app, click on the green debug button and a
|
||||
green debug button should appear next to the app's name. You can now set breakpoints and step through the source code.
|
||||
|
||||
## Windows / Linux SNAPSHOT build
|
||||
|
||||
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...
|
||||
- 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.
|
||||
|
||||
The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desktop-[version]-SNAPSHOT
|
||||
|
||||
# IntelliJ
|
||||
|
||||
TBD
|
||||
|
||||
# Card Scripting
|
||||
|
||||
Visit [this page](https://www.slightlymagic.net/wiki/Forge_API) for information on scripting.
|
||||
|
||||
Card scripting resources are found in the forge-gui/res/ path.
|
||||
|
||||
# General Notes
|
||||
|
||||
## Project Hierarchy
|
||||
|
||||
Forge is divided into 4 primary projects with additional projects that target specific platform releases. The primary projects are:
|
||||
|
||||
- forge-ai
|
||||
- forge-core
|
||||
- forge-game
|
||||
- forge-gui
|
||||
|
||||
The platform-specific projects are:
|
||||
|
||||
- forge-gui-android
|
||||
- forge-gui-desktop
|
||||
- forge-gui-ios
|
||||
- forge-gui-mobile
|
||||
- forge-gui-mobile-dev
|
||||
|
||||
### forge-ai
|
||||
|
||||
### forge-core
|
||||
|
||||
### forge-game
|
||||
|
||||
### forge-gui
|
||||
|
||||
The forge-gui project includes the scripting resource definitions in the res/ path.
|
||||
|
||||
### forge-gui-android
|
||||
|
||||
Libgdx-based backend targeting Android. Requires Android SDK and relies on forge-gui-mobile for GUI logic.
|
||||
|
||||
### forge-gui-desktop
|
||||
|
||||
Java Swing based GUI targeting desktop machines.
|
||||
|
||||
Screen layout and game logic revolving around the GUI is found here. For example, the overlay arrows (when enabled) that indicate attackers and blockers, or the targets of the stack are defined and drawn by this.
|
||||
|
||||
### forge-gui-ios
|
||||
|
||||
Libgdx-based backend targeting iOS. Relies on forge-gui-mobile for GUI logic.
|
||||
|
||||
### forge-gui-mobile
|
||||
|
||||
Mobile GUI game logic utilizing [libgdx](https://libgdx.badlogicgames.com/) library. Screen layout and game logic revolving around the GUI for the mobile platforms is found here.
|
||||
|
||||
### forge-gui-mobile-dev
|
||||
|
||||
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/forge-core"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>forge-ai</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
<project>forge-game</project>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
@@ -1,3 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=ISO-8859-1
|
||||
encoding/<project>=UTF-8
|
||||
@@ -1,5 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.source=1.7
|
||||
@@ -1,4 +0,0 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.20-SNAPSHOT</version>
|
||||
<version>1.6.23</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
|
||||
@@ -1211,7 +1211,7 @@ public class AiController {
|
||||
public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
ApiType api = sa.getApi();
|
||||
|
||||
// Abilities without api may also use this routine, However they should provide a unique mode value
|
||||
// Abilities without api may also use this routine, However they should provide a unique mode value ?? How could this work?
|
||||
if (api == null) {
|
||||
String exMsg = String.format("AI confirmAction does not know what to decide about %s mode (api is null).",
|
||||
mode);
|
||||
|
||||
@@ -821,6 +821,8 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (sVar.equals("Count$xPaid")) {
|
||||
c = AbilityUtils.calculateAmount(source, "PayX", null);
|
||||
} else {
|
||||
c = AbilityUtils.calculateAmount(source, amount, ability);
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ public class ComputerUtil {
|
||||
public static boolean handlePlayingSpellAbility(final Player ai, SpellAbility sa, final Game game, Runnable chooseTargets) {
|
||||
game.getStack().freezeStack();
|
||||
final Card source = sa.getHostCard();
|
||||
source.setSplitStateToPlayAbility(sa);
|
||||
|
||||
if (sa.isSpell() && !source.isCopiedSpell()) {
|
||||
if (source.getType().hasStringType("Arcane")) {
|
||||
@@ -2847,7 +2848,7 @@ public class ComputerUtil {
|
||||
repParams.put("Source", source);
|
||||
|
||||
List<ReplacementEffect> list = player.getGame().getReplacementHandler().getReplacementList(repParams,
|
||||
ReplacementLayer.None);
|
||||
ReplacementLayer.Other);
|
||||
|
||||
if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "NoLife"))) {
|
||||
return false;
|
||||
@@ -2878,7 +2879,7 @@ public class ComputerUtil {
|
||||
repParams.put("Source", source);
|
||||
|
||||
List<ReplacementEffect> list = player.getGame().getReplacementHandler().getReplacementList(repParams,
|
||||
ReplacementLayer.None);
|
||||
ReplacementLayer.Other);
|
||||
|
||||
if (Iterables.any(list, CardTraitPredicates.hasParam("AiLogic", "NoLife"))) {
|
||||
// no life gain is not negative
|
||||
|
||||
@@ -95,9 +95,15 @@ public class ComputerUtilAbility {
|
||||
|
||||
public static List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList, final Player player) {
|
||||
final List<SpellAbility> newAbilities = Lists.newArrayList();
|
||||
for (SpellAbility sa : originList) {
|
||||
sa.setActivatingPlayer(player);
|
||||
|
||||
List<SpellAbility> originListWithAddCosts = Lists.newArrayList();
|
||||
for (SpellAbility sa : originList) {
|
||||
// If this spell has alternative additional costs, add them instead of the unmodified SA itself
|
||||
sa.setActivatingPlayer(player);
|
||||
originListWithAddCosts.addAll(GameActionUtil.getAdditionalCostSpell(sa));
|
||||
}
|
||||
|
||||
for (SpellAbility sa : originListWithAddCosts) {
|
||||
// determine which alternative costs are cheaper than the original and prioritize them
|
||||
List<SpellAbility> saAltCosts = GameActionUtil.getAlternativeCosts(sa, player);
|
||||
List<SpellAbility> priorityAltSa = Lists.newArrayList();
|
||||
|
||||
@@ -1455,7 +1455,7 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ability.hasParam("Adapt") && blocker != null && blocker.getCounters(CounterType.P1P1) > 0) {
|
||||
if (ability.hasParam("Adapt") && attacker.getCounters(CounterType.P1P1) > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1692,7 +1692,7 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ability.hasParam("Adapt") && blocker.getCounters(CounterType.P1P1) > 0) {
|
||||
if (ability.hasParam("Adapt") && attacker.getCounters(CounterType.P1P1) > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2590,7 +2590,7 @@ public class ComputerUtilCombat {
|
||||
// repParams.put("PreventMap", preventMap);
|
||||
|
||||
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(repParams,
|
||||
ReplacementLayer.None);
|
||||
ReplacementLayer.Other);
|
||||
|
||||
return !list.isEmpty();
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ public abstract class GameState {
|
||||
private final Map<Card, Integer> cardToEnchantPlayerId = new HashMap<>();
|
||||
private final Map<Card, Integer> markedDamage = new HashMap<>();
|
||||
private final Map<Card, List<String>> cardToChosenClrs = new HashMap<>();
|
||||
private final Map<Card, CardCollection> cardToChosenCards = new HashMap<>();
|
||||
private final Map<Card, String> cardToChosenType = new HashMap<>();
|
||||
private final Map<Card, List<String>> cardToRememberedId = new HashMap<>();
|
||||
private final Map<Card, List<String>> cardToImprintedId = new HashMap<>();
|
||||
@@ -98,6 +99,8 @@ public abstract class GameState {
|
||||
|
||||
private int turn = 1;
|
||||
|
||||
private boolean removeSummoningSickness = false;
|
||||
|
||||
// Targeting for precast spells in a game state (mostly used by Puzzle Mode game states)
|
||||
private final int TARGET_NONE = -1; // untargeted spell (e.g. Joraga Invocation)
|
||||
private final int TARGET_HUMAN = -2;
|
||||
@@ -211,6 +214,10 @@ public abstract class GameState {
|
||||
// Remember the IDs of imprinted cards
|
||||
cardsReferencedByID.add(i);
|
||||
}
|
||||
for (Card i : card.getChosenCards()) {
|
||||
// Remember the IDs of chosen cards
|
||||
cardsReferencedByID.add(i);
|
||||
}
|
||||
if (game.getCombat() != null && game.getCombat().isAttacking(card)) {
|
||||
// Remember the IDs of attacked planeswalkers
|
||||
GameEntity def = game.getCombat().getDefenderByAttacker(card);
|
||||
@@ -312,6 +319,17 @@ public abstract class GameState {
|
||||
newText.append("|NamedCard:").append(c.getNamedCard());
|
||||
}
|
||||
|
||||
List<String> chosenCardIds = Lists.newArrayList();
|
||||
for (Object obj : c.getChosenCards()) {
|
||||
if (obj instanceof Card) {
|
||||
int id = ((Card)obj).getId();
|
||||
chosenCardIds.add(String.valueOf(id));
|
||||
}
|
||||
}
|
||||
if (!chosenCardIds.isEmpty()) {
|
||||
newText.append("|ChosenCards:").append(TextUtil.join(chosenCardIds, ","));
|
||||
}
|
||||
|
||||
List<String> rememberedCardIds = Lists.newArrayList();
|
||||
for (Object obj : c.getRemembered()) {
|
||||
if (obj instanceof Card) {
|
||||
@@ -432,6 +450,10 @@ public abstract class GameState {
|
||||
turn = Integer.parseInt(categoryValue);
|
||||
}
|
||||
|
||||
else if (categoryName.equals("removesummoningsickness")) {
|
||||
removeSummoningSickness = categoryValue.equalsIgnoreCase("true");
|
||||
}
|
||||
|
||||
else if (categoryName.endsWith("life")) {
|
||||
if (isHuman)
|
||||
humanLife = Integer.parseInt(categoryValue);
|
||||
@@ -552,6 +574,7 @@ public abstract class GameState {
|
||||
cardToExiledWithId.clear();
|
||||
markedDamage.clear();
|
||||
cardToChosenClrs.clear();
|
||||
cardToChosenCards.clear();
|
||||
cardToChosenType.clear();
|
||||
cardToScript.clear();
|
||||
cardAttackMap.clear();
|
||||
@@ -605,6 +628,12 @@ public abstract class GameState {
|
||||
game.getPhaseHandler().devAdvanceToPhase(advPhase);
|
||||
}
|
||||
|
||||
if (removeSummoningSickness) {
|
||||
for (Card card : game.getCardsInGame()) {
|
||||
card.setSickness(false);
|
||||
}
|
||||
}
|
||||
|
||||
game.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
|
||||
}
|
||||
|
||||
@@ -947,6 +976,12 @@ public abstract class GameState {
|
||||
Card c = entry.getKey();
|
||||
c.setNamedCard(entry.getValue());
|
||||
}
|
||||
|
||||
// Chosen cards
|
||||
for (Entry<Card, CardCollection> entry : cardToChosenCards.entrySet()) {
|
||||
Card c = entry.getKey();
|
||||
c.setChosenCards(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCardAttachments() {
|
||||
@@ -1047,7 +1082,6 @@ public abstract class GameState {
|
||||
zone.setCards(kv.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1143,6 +1177,13 @@ public abstract class GameState {
|
||||
cardToChosenClrs.put(c, Arrays.asList(info.substring(info.indexOf(':') + 1).split(",")));
|
||||
} else if (info.startsWith("ChosenType:")) {
|
||||
cardToChosenType.put(c, info.substring(info.indexOf(':') + 1));
|
||||
} else if (info.startsWith("ChosenCards:")) {
|
||||
CardCollection chosen = new CardCollection();
|
||||
String[] idlist = info.substring(info.indexOf(':') + 1).split(",");
|
||||
for (String id : idlist) {
|
||||
chosen.add(idToCard.get(Integer.parseInt(id)));
|
||||
}
|
||||
cardToChosenCards.put(c, chosen);
|
||||
} else if (info.startsWith("NamedCard:")) {
|
||||
cardToNamedCard.put(c, info.substring(info.indexOf(':') + 1));
|
||||
} else if (info.startsWith("ExecuteScript:")) {
|
||||
|
||||
@@ -162,10 +162,36 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
@Override
|
||||
public <T extends GameEntity> List<T> chooseEntitiesForEffect(
|
||||
FCollectionView<T> optionList, DelayedReveal delayedReveal, SpellAbility sa, String title,
|
||||
FCollectionView<T> optionList, int min, int max, DelayedReveal delayedReveal, SpellAbility sa, String title,
|
||||
Player targetedPlayer) {
|
||||
// this isn't used
|
||||
return null;
|
||||
if (delayedReveal != null) {
|
||||
reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix());
|
||||
}
|
||||
FCollection<T> remaining = new FCollection<T>(optionList);
|
||||
List<T> selecteds = new ArrayList<T>();
|
||||
T selected;
|
||||
do {
|
||||
selected = chooseSingleEntityForEffect(remaining, null, sa, title, selecteds.size()>=min, targetedPlayer);
|
||||
if ( selected != null ) {
|
||||
remaining.remove(selected);
|
||||
selecteds.add(selected);
|
||||
}
|
||||
} while ( (selected != null ) && (selecteds.size() < max) );
|
||||
return selecteds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends GameEntity> List<T> chooseFromTwoListsForEffect(FCollectionView<T> optionList1, FCollectionView<T> optionList2,
|
||||
boolean optional, DelayedReveal delayedReveal, SpellAbility sa, String title, Player targetedPlayer) {
|
||||
if (delayedReveal != null) {
|
||||
reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix());
|
||||
}
|
||||
T selected1 = chooseSingleEntityForEffect(optionList1, null, sa, title, optional, targetedPlayer);
|
||||
T selected2 = chooseSingleEntityForEffect(optionList2, null, sa, title, optional || selected1!=null, targetedPlayer);
|
||||
List<T> selecteds = new ArrayList<T>();
|
||||
if ( selected1 != null ) { selecteds.add(selected1); }
|
||||
if ( selected2 != null ) { selecteds.add(selected2); }
|
||||
return selecteds;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1090,7 +1116,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
@Override
|
||||
public List<Card> chooseCardsForZoneChange(
|
||||
ZoneType destination, List<ZoneType> origin, SpellAbility sa, CardCollection fetchList,
|
||||
ZoneType destination, List<ZoneType> origin, SpellAbility sa, CardCollection fetchList, int min, int max,
|
||||
DelayedReveal delayedReveal, String selectPrompt, Player decider) {
|
||||
// this isn't used
|
||||
return null;
|
||||
@@ -1164,4 +1190,10 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
return chosenOptCosts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmMulliganScry(Player p) {
|
||||
// Always true?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,8 +233,15 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
boolean alternativeConsiderations = hasFloatMana || willDiscardNow || willDieNow || willRespondToStack || willCastAtEOT || willCastEarly;
|
||||
|
||||
if (!alternativeConsiderations && (combat == null || game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) || (!combat.isAttacking(attachTarget) && !combat.isBlocking(attachTarget))) {
|
||||
return false;
|
||||
if (!alternativeConsiderations) {
|
||||
if (combat == null ||
|
||||
game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(combat.isAttacking(attachTarget) || combat.isBlocking(attachTarget))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -311,13 +311,20 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.hasParam("Adapt") && source.getCounters(CounterType.P1P1) > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO handle proper calculation of X values based on Cost
|
||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
|
||||
if (sa.hasParam("Adapt")) {
|
||||
Game game = ai.getGame();
|
||||
Combat combat = game.getCombat();
|
||||
|
||||
if (!source.canReceiveCounters(CounterType.P1P1) || source.getCounters(CounterType.P1P1) > 0) {
|
||||
return false;
|
||||
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return doCombatAdaptLogic(source, amount, combat);
|
||||
}
|
||||
}
|
||||
|
||||
if ("Fight".equals(logic)) {
|
||||
int nPump = 0;
|
||||
if (type.equals("P1P1")) {
|
||||
@@ -1049,4 +1056,39 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean doCombatAdaptLogic(Card source, int amount, Combat combat) {
|
||||
if (combat.isAttacking(source)) {
|
||||
if (!combat.isBlocked(source)) {
|
||||
return true;
|
||||
} else {
|
||||
for (Card blockedBy : combat.getBlockers(source)) {
|
||||
if (blockedBy.getNetToughness() > source.getNetPower()
|
||||
&& blockedBy.getNetToughness() <= source.getNetPower() + amount) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int totBlkPower = Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower);
|
||||
if (source.getNetToughness() <= totBlkPower
|
||||
&& source.getNetToughness() + amount > totBlkPower) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (combat.isBlocking(source)) {
|
||||
for (Card blocked : combat.getAttackersBlockedBy(source)) {
|
||||
if (blocked.getNetToughness() > source.getNetPower()
|
||||
&& blocked.getNetToughness() <= source.getNetPower() + amount) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int totAtkPower = Aggregates.sum(combat.getAttackersBlockedBy(source), CardPredicates.Accessors.fnGetNetPower);
|
||||
if (source.getNetToughness() <= totAtkPower
|
||||
&& source.getNetToughness() + amount > totAtkPower) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostPartMana;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.keyword.Keyword;
|
||||
@@ -286,6 +287,18 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
}
|
||||
|
||||
if ("XCountersDamage".equals(logic) && sa.getPayCosts() != null) {
|
||||
// Check to ensure that we have enough counters to remove per the defined PayX
|
||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
if (source.getCounters(((CostRemoveCounter) part).counter) < Integer.valueOf(source.getSVar("PayX"))) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -635,10 +648,9 @@ public class DamageDealAi extends DamageAiBase {
|
||||
if (c != null && !this.shouldTgtP(ai, sa, dmg, noPrevention, true)) {
|
||||
tcs.add(c);
|
||||
if (divided) {
|
||||
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||
if (assignedDamage <= dmg) {
|
||||
tgt.addDividedAllocation(c, assignedDamage);
|
||||
}
|
||||
int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
|
||||
assignedDamage = Math.min(dmg, assignedDamage);
|
||||
tgt.addDividedAllocation(c, assignedDamage);
|
||||
dmg = dmg - assignedDamage;
|
||||
if (dmg <= 0) {
|
||||
break;
|
||||
@@ -1080,7 +1092,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
continue; // not a toughness debuff
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNumeric(dmgDef) && ab.canPlay()) { // currently doesn't work for X and other dependent costs
|
||||
if (StringUtils.isNumeric(dmgDef)) { // currently doesn't work for X and other dependent costs
|
||||
if (sa.usesTargeting() && ab.usesTargeting()) {
|
||||
// Ensure that the chained spell can target at least the same things (or more) as the current one
|
||||
TargetRestrictions tgtSa = sa.getTargetRestrictions();
|
||||
|
||||
@@ -51,6 +51,13 @@ public class LifeLoseAi extends SpellAbilityAi {
|
||||
if (tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
if (!doTgt(ai, sa, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ public class ManifestAi extends SpellAbilityAi {
|
||||
repParams.put("Origin", ZoneType.Library);
|
||||
repParams.put("Destination", ZoneType.Battlefield);
|
||||
repParams.put("Source", sa.getHostCard());
|
||||
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(repParams, ReplacementLayer.None);
|
||||
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(repParams, ReplacementLayer.Other);
|
||||
if (!list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
@@ -26,12 +24,21 @@ public class TapAi extends TapAiBase {
|
||||
|
||||
if (turn.isOpponentOf(ai) && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
// Tap things down if it's Human's turn
|
||||
} else if (turn == ai && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
// Tap creatures down if in combat -- handled in tapPrefTargeting().
|
||||
} else if (SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
// Cast it if it's a sorcery.
|
||||
} else if (turn.equals(ai)) {
|
||||
if (SpellAbilityAi.isSorcerySpeed(sa) && phase.getPhase().isBefore(PhaseType.COMBAT_BEGIN)) {
|
||||
// Cast it if it's a sorcery.
|
||||
} else if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
// Aggro Brains are willing to use TapEffects aggressively instead of defensively
|
||||
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
|
||||
if (!aic.getBooleanProperty(AiProps.PLAY_AGGRO)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Don't tap down after blockers
|
||||
return false;
|
||||
}
|
||||
} else if (!SpellAbilityAi.playReusable(ai, sa)){
|
||||
// Generally don't want to tap things with an Instant during AI turn outside of combat
|
||||
// Generally don't want to tap things with an Instant during Players turn outside of combat
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,10 @@ import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.TokenEffect;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.CostPart;
|
||||
@@ -58,20 +60,9 @@ public class TokenAi extends SpellAbilityAi {
|
||||
private void readParameters(final SpellAbility mapParams) {
|
||||
this.tokenAmount = mapParams.getParamOrDefault("TokenAmount", "1");
|
||||
|
||||
TokenEffect effect = new TokenEffect();
|
||||
|
||||
this.actualToken = effect.loadTokenPrototype(mapParams);
|
||||
this.actualToken = TokenInfo.getProtoType(mapParams.getParam("TokenScript"), mapParams);
|
||||
|
||||
if (actualToken == null) {
|
||||
String[] keywords;
|
||||
|
||||
if (mapParams.hasParam("TokenKeywords")) {
|
||||
// TODO: Change this Split to a semicolon or something else
|
||||
keywords = mapParams.getParam("TokenKeywords").split("<>");
|
||||
} else {
|
||||
keywords = new String[0];
|
||||
}
|
||||
|
||||
this.tokenPower = mapParams.getParam("TokenPower");
|
||||
this.tokenToughness = mapParams.getParam("TokenToughness");
|
||||
} else {
|
||||
@@ -340,6 +331,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
// TODO: AILogic
|
||||
readParameters(sa); // remember to call this somewhere!
|
||||
Combat combat = ai.getGame().getCombat();
|
||||
// TokenAttacking
|
||||
if (combat != null && sa.hasParam("TokenAttacking")) {
|
||||
@@ -359,6 +351,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options) {
|
||||
// TODO: AILogic
|
||||
readParameters(sa); // remember to call this somewhere!
|
||||
Combat combat = ai.getGame().getCombat();
|
||||
// TokenAttacking
|
||||
if (combat != null && sa.hasParam("TokenAttacking")) {
|
||||
@@ -389,6 +382,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
* @param sa Token SpellAbility
|
||||
* @return token creature created by ability
|
||||
*/
|
||||
@Deprecated
|
||||
public static Card spawnToken(Player ai, SpellAbility sa) {
|
||||
return spawnToken(ai, sa, false);
|
||||
}
|
||||
@@ -401,9 +395,17 @@ public class TokenAi extends SpellAbilityAi {
|
||||
* @return token creature created by ability
|
||||
*/
|
||||
// TODO Is this just completely copied from TokenEffect? Let's just call that thing
|
||||
@Deprecated
|
||||
public static Card spawnToken(Player ai, SpellAbility sa, boolean notNull) {
|
||||
final Card host = sa.getHostCard();
|
||||
|
||||
Card result = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa);
|
||||
|
||||
if (result != null) {
|
||||
result.setController(ai, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
String[] tokenKeywords = sa.hasParam("TokenKeywords") ? sa.getParam("TokenKeywords").split("<>") : new String[0];
|
||||
String tokenPower = sa.getParam("TokenPower");
|
||||
String tokenToughness = sa.getParam("TokenToughness");
|
||||
|
||||
@@ -310,7 +310,8 @@ public class GameCopier {
|
||||
newCard.setManifested(true);
|
||||
// TODO: Should be able to copy other abilities...
|
||||
if (isCreature && hasManaCost) {
|
||||
newCard.addSpellAbility(CardFactoryUtil.abilityManifestFaceUp(newCard, newCard.getManaCost()));
|
||||
newCard.getState(CardStateName.Original).addSpellAbility(
|
||||
CardFactoryUtil.abilityManifestFaceUp(newCard, newCard.getManaCost()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>forge-core</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
@@ -1,4 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=ISO-8859-1
|
||||
encoding//src/test/java=ISO-8859-1
|
||||
encoding/<project>=UTF-8
|
||||
@@ -1,5 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.source=1.7
|
||||
@@ -1,4 +0,0 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.20-SNAPSHOT</version>
|
||||
<version>1.6.23</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-core</artifactId>
|
||||
|
||||
@@ -21,7 +21,6 @@ import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.StaticData;
|
||||
import forge.card.CardDb.SetPreference;
|
||||
import forge.deck.CardPool;
|
||||
@@ -31,7 +30,6 @@ import forge.util.*;
|
||||
import forge.util.storage.StorageBase;
|
||||
import forge.util.storage.StorageReaderBase;
|
||||
import forge.util.storage.StorageReaderFolder;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -123,17 +121,17 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
private boolean smallSetOverride = false;
|
||||
private String boosterMustContain = "";
|
||||
private final CardInSet[] cards;
|
||||
private final String[] tokenNormalized;
|
||||
private final Map<String, Integer> tokenNormalized;
|
||||
|
||||
private int boosterArts = 1;
|
||||
private SealedProduct.Template boosterTpl = null;
|
||||
|
||||
private CardEdition(CardInSet[] cards) {
|
||||
this.cards = cards;
|
||||
tokenNormalized = null;
|
||||
tokenNormalized = new HashMap<>();
|
||||
}
|
||||
|
||||
private CardEdition(CardInSet[] cards, String[] tokens) {
|
||||
private CardEdition(CardInSet[] cards, Map<String, Integer> tokens) {
|
||||
this.cards = cards;
|
||||
this.tokenNormalized = tokens;
|
||||
}
|
||||
@@ -191,6 +189,8 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
public String getBoosterMustContain() { return boosterMustContain; }
|
||||
public CardInSet[] getCards() { return cards; }
|
||||
|
||||
public Map<String, Integer> getTokens() { return tokenNormalized; };
|
||||
|
||||
public static final Function<CardEdition, String> FN_GET_CODE = new Function<CardEdition, String>() {
|
||||
@Override
|
||||
public String apply(final CardEdition arg1) {
|
||||
@@ -261,7 +261,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
protected CardEdition read(File file) {
|
||||
final Map<String, List<String>> contents = FileSection.parseSections(FileUtil.readFile(file));
|
||||
|
||||
List<String> tokenNormalized = new ArrayList<>();
|
||||
Map<String, Integer> tokenNormalized = new HashMap<>();
|
||||
List<CardEdition.CardInSet> processedCards = new ArrayList<>();
|
||||
if (contents.containsKey("cards")) {
|
||||
for(String line : contents.get("cards")) {
|
||||
@@ -290,13 +290,17 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
if (StringUtils.isBlank(line))
|
||||
continue;
|
||||
|
||||
tokenNormalized.add(line);
|
||||
if (!tokenNormalized.containsKey(line)) {
|
||||
tokenNormalized.put(line, 1);
|
||||
} else {
|
||||
tokenNormalized.put(line, tokenNormalized.get(line) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CardEdition res = new CardEdition(
|
||||
processedCards.toArray(new CardInSet[processedCards.size()]),
|
||||
tokenNormalized.toArray(new String[tokenNormalized.size()])
|
||||
tokenNormalized
|
||||
);
|
||||
|
||||
FileSection section = FileSection.parse(contents.get("metadata"), "=");
|
||||
|
||||
@@ -581,12 +581,11 @@ public final class CardRulesPredicates {
|
||||
public static final Predicate<CardRules> IS_VANGUARD = CardRulesPredicates.coreType(true, CardType.CoreType.Vanguard);
|
||||
public static final Predicate<CardRules> IS_CONSPIRACY = CardRulesPredicates.coreType(true, CardType.CoreType.Conspiracy);
|
||||
public static final Predicate<CardRules> IS_NON_LAND = CardRulesPredicates.coreType(false, CardType.CoreType.Land);
|
||||
public static final Predicate<CardRules> IS_NON_CREATURE_SPELL = Predicates.not(Predicates.or(Presets.IS_CREATURE, Presets.IS_LAND));
|
||||
public static final Predicate<CardRules> CAN_BE_BRAWL_COMMANDER = Predicates.or(Presets.IS_PLANESWALKER,
|
||||
Predicates.and(Presets.IS_CREATURE, Presets.IS_LEGENDARY));
|
||||
|
||||
/** The Constant IS_NONCREATURE_SPELL_FOR_GENERATOR. **/
|
||||
public static final Predicate<CardRules> IS_NONCREATURE_SPELL_FOR_GENERATOR = com.google.common.base.Predicates
|
||||
/** The Constant IS_NON_CREATURE_SPELL. **/
|
||||
public static final Predicate<CardRules> IS_NON_CREATURE_SPELL = com.google.common.base.Predicates
|
||||
.or(Presets.IS_SORCERY, Presets.IS_INSTANT, Presets.IS_PLANESWALKER, Presets.IS_ENCHANTMENT,
|
||||
Predicates.and(Presets.IS_ARTIFACT, Predicates.not(Presets.IS_CREATURE)));
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ package forge.deck;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.StaticData;
|
||||
import forge.card.CardRules;
|
||||
import forge.card.CardRulesPredicates;
|
||||
@@ -47,7 +49,7 @@ public enum DeckFormat {
|
||||
QuestDeck ( Range.between(40, Integer.MAX_VALUE), Range.between(0, 15), 4),
|
||||
Limited ( Range.between(40, Integer.MAX_VALUE), null, Integer.MAX_VALUE),
|
||||
Commander ( Range.is(99), Range.between(0, 10), 1, new Predicate<CardRules>() {
|
||||
private final Set<String> bannedCards = new HashSet<String>(Arrays.asList(
|
||||
private final Set<String> bannedCards = ImmutableSet.of(
|
||||
"Adriana's Valor", "Advantageous Proclamation", "Amulet of Quoz", "Ancestral Recall", "Assemble the Rank and Vile",
|
||||
"Backup Plan", "Balance", "Biorhythm", "Black Lotus", "Brago's Favor", "Braids, Cabal Minion", "Bronze Tablet",
|
||||
"Channel", "Chaos Orb", "Coalition Victory", "Contract from Below", "Darkpact", "Demonic Attorney", "Double Stroke",
|
||||
@@ -59,7 +61,7 @@ public enum DeckFormat {
|
||||
"Rebirth", "Recurring Nightmare", "Rofellos, Llanowar Emissary", "Secret Summoning", "Secrets of Paradise",
|
||||
"Sentinel Dispatch", "Shahrazad", "Sovereign's Realm", "Summoner's Bond", "Sundering Titan", "Sway of the Stars",
|
||||
"Sylvan Primordial", "Tempest Efreet", "Time Vault", "Time Walk", "Timmerian Fiends", "Tinker", "Tolarian Academy",
|
||||
"Trade Secrets", "Unexpected Potential", "Upheaval", "Weight Advantage", "Worldfire", "Worldknit", "Yawgmoth's Bargain"));
|
||||
"Trade Secrets", "Unexpected Potential", "Upheaval", "Weight Advantage", "Worldfire", "Worldknit", "Yawgmoth's Bargain");
|
||||
@Override
|
||||
public boolean apply(CardRules rules) {
|
||||
if (bannedCards.contains(rules.getName())) {
|
||||
@@ -70,8 +72,8 @@ public enum DeckFormat {
|
||||
}),
|
||||
Pauper ( Range.is(60), Range.between(0, 10), 1),
|
||||
Brawl ( Range.is(59), Range.between(0, 15), 1, null, new Predicate<PaperCard>() {
|
||||
private final Set<String> bannedCards = new HashSet<String>(Arrays.asList(
|
||||
"Baral, Chief of Compliance","Smuggler's Copter","Sorcerous Spyglass"));
|
||||
private final Set<String> bannedCards = ImmutableSet.of(
|
||||
"Baral, Chief of Compliance","Smuggler's Copter","Sorcerous Spyglass");
|
||||
@Override
|
||||
public boolean apply(PaperCard card) {
|
||||
//why do we need to hard code the bannings here - they are defined in the GameFormat predicate used below
|
||||
@@ -81,7 +83,7 @@ public enum DeckFormat {
|
||||
return StaticData.instance() == null ? false : StaticData.instance().getBrawlPredicate().apply(card);
|
||||
}
|
||||
}) {
|
||||
private final ImmutableSet<String> bannedCommanders = ImmutableSet.of("Baral, Chief of Compliance");
|
||||
private final Set<String> bannedCommanders = ImmutableSet.of("Baral, Chief of Compliance");
|
||||
|
||||
@Override
|
||||
public boolean isLegalCommander(CardRules rules) {
|
||||
@@ -89,11 +91,11 @@ public enum DeckFormat {
|
||||
}
|
||||
},
|
||||
TinyLeaders ( Range.is(49), Range.between(0, 10), 1, new Predicate<CardRules>() {
|
||||
private final Set<String> bannedCards = new HashSet<String>(Arrays.asList(
|
||||
private final Set<String> bannedCards = ImmutableSet.of(
|
||||
"Ancestral Recall", "Balance", "Black Lotus", "Black Vise", "Channel", "Chaos Orb", "Contract From Below", "Counterbalance", "Darkpact", "Demonic Attorney", "Demonic Tutor", "Earthcraft", "Edric, Spymaster of Trest", "Falling Star",
|
||||
"Fastbond", "Flash", "Goblin Recruiter", "Grindstone", "Hermit Druid", "Imperial Seal", "Jeweled Bird", "Karakas", "Library of Alexandria", "Mana Crypt", "Mana Drain", "Mana Vault", "Metalworker", "Mind Twist", "Mishra's Workshop",
|
||||
"Mox Emerald", "Mox Jet", "Mox Pearl", "Mox Ruby", "Mox Sapphire", "Necropotence", "Shahrazad", "Skullclamp", "Sol Ring", "Strip Mine", "Survival of the Fittest", "Sword of Body and Mind", "Time Vault", "Time Walk", "Timetwister",
|
||||
"Timmerian Fiends", "Tolarian Academy", "Umezawa's Jitte", "Vampiric Tutor", "Wheel of Fortune", "Yawgmoth's Will"));
|
||||
"Timmerian Fiends", "Tolarian Academy", "Umezawa's Jitte", "Vampiric Tutor", "Wheel of Fortune", "Yawgmoth's Will");
|
||||
|
||||
@Override
|
||||
public boolean apply(CardRules rules) {
|
||||
@@ -112,7 +114,7 @@ public enum DeckFormat {
|
||||
return true;
|
||||
}
|
||||
}) {
|
||||
private final ImmutableSet<String> bannedCommanders = ImmutableSet.of("Derevi, Empyrial Tactician", "Erayo, Soratami Ascendant", "Rofellos, Llanowar Emissary");
|
||||
private final Set<String> bannedCommanders = ImmutableSet.of("Derevi, Empyrial Tactician", "Erayo, Soratami Ascendant", "Rofellos, Llanowar Emissary");
|
||||
|
||||
@Override
|
||||
public boolean isLegalCommander(CardRules rules) {
|
||||
@@ -141,13 +143,6 @@ public enum DeckFormat {
|
||||
private final static String ADVPROCLAMATION = "Advantageous Proclamation";
|
||||
private final static String SOVREALM = "Sovereign's Realm";
|
||||
|
||||
private static final List<String> limitExceptions = Arrays.asList(
|
||||
new String[]{"Relentless Rats", "Shadowborn Apostle", "Rat Colony"});
|
||||
|
||||
public static List<String> getLimitExceptions(){
|
||||
return limitExceptions;
|
||||
}
|
||||
|
||||
private DeckFormat(Range<Integer> mainRange0, Range<Integer> sideRange0, int maxCardCopies0, Predicate<CardRules> cardPoolFilter0, Predicate<PaperCard> paperCardPoolFilter0) {
|
||||
mainRange = mainRange0;
|
||||
sideRange = sideRange0;
|
||||
@@ -342,7 +337,6 @@ public enum DeckFormat {
|
||||
//basic lands, Shadowborn Apostle, Relentless Rats and Rat Colony
|
||||
|
||||
final CardPool allCards = deck.getAllCardsInASinglePool(hasCommander());
|
||||
final ImmutableSet<String> limitExceptions = ImmutableSet.of("Relentless Rats", "Shadowborn Apostle", "Rat Colony");
|
||||
|
||||
// should group all cards by name, so that different editions of same card are really counted as the same card
|
||||
for (final Entry<String, Integer> cp : Aggregates.groupSumBy(allCards, PaperCard.FN_GET_NAME)) {
|
||||
@@ -351,8 +345,7 @@ public enum DeckFormat {
|
||||
return TextUtil.concatWithSpace("contains the nonexisting card", cp.getKey());
|
||||
}
|
||||
|
||||
final boolean canHaveMultiple = simpleCard.getRules().getType().isBasicLand() || limitExceptions.contains(cp.getKey());
|
||||
if (!canHaveMultiple && cp.getValue() > maxCopies) {
|
||||
if (!canHaveAnyNumberOf(simpleCard) && cp.getValue() > maxCopies) {
|
||||
return TextUtil.concatWithSpace("must not contain more than", String.valueOf(maxCopies), "copies of the card", cp.getKey());
|
||||
}
|
||||
}
|
||||
@@ -370,6 +363,12 @@ public enum DeckFormat {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean canHaveAnyNumberOf(final IPaperCard icard) {
|
||||
return icard.getRules().getType().isBasicLand()
|
||||
|| Iterables.contains(icard.getRules().getMainPart().getKeywords(),
|
||||
"A deck can have any number of cards named CARDNAME.");
|
||||
}
|
||||
|
||||
public static String getPlaneSectionConformanceProblem(final CardPool planes) {
|
||||
//Must contain at least 10 planes/phenomenons, but max 2 phenomenons. Singleton.
|
||||
if (planes == null || planes.countAll() < 10) {
|
||||
|
||||
@@ -102,6 +102,7 @@ public class DeckRecognizer {
|
||||
// Pattern.compile("(.*)[^A-Za-wyz]*\\s+([\\d]{1,2})");
|
||||
private static final Pattern SEARCH_NUMBERS_IN_FRONT = Pattern.compile("([\\d]{1,2})[^A-Za-wyz]*\\s+(.*)");
|
||||
//private static final Pattern READ_SEPARATED_EDITION = Pattern.compile("[[\\(\\{]([a-zA-Z0-9]){1,3})[]*\\s+(.*)");
|
||||
private static final Pattern SEARCH_SINGLE_SLASH = Pattern.compile("(?<=[^/])\\s*/\\s*(?=[^/])");
|
||||
|
||||
private final SetPreference useLastSet;
|
||||
private final ICardDatabase db;
|
||||
@@ -125,7 +126,10 @@ public class DeckRecognizer {
|
||||
return new Token(TokenType.Comment, 0, rawLine);
|
||||
}
|
||||
final char smartQuote = (char) 8217;
|
||||
final String line = rawLine.trim().replace(smartQuote, '\'');
|
||||
String line = rawLine.trim().replace(smartQuote, '\'');
|
||||
|
||||
// Some websites export split card names with a single slash. Replace with double slash.
|
||||
line = SEARCH_SINGLE_SLASH.matcher(line).replaceFirst(" // ");
|
||||
|
||||
Token result = null;
|
||||
final Matcher foundNumbersInFront = DeckRecognizer.SEARCH_NUMBERS_IN_FRONT.matcher(line);
|
||||
|
||||
@@ -100,7 +100,7 @@ public abstract class DeckGeneratorBase {
|
||||
trace.append("Creatures to add:").append(creatCnt).append("\n");
|
||||
addCmcAdjusted(creatures, creatCnt, cmcLevels);
|
||||
|
||||
Predicate<PaperCard> preSpells = Predicates.compose(CardRulesPredicates.Presets.IS_NONCREATURE_SPELL_FOR_GENERATOR, PaperCard.FN_GET_RULES);
|
||||
Predicate<PaperCard> preSpells = Predicates.compose(CardRulesPredicates.Presets.IS_NON_CREATURE_SPELL, PaperCard.FN_GET_RULES);
|
||||
final Iterable<PaperCard> spells = Iterables.filter(cards, preSpells);
|
||||
final int spellCnt = (int) Math.ceil(getSpellPercentage() * size);
|
||||
trace.append("Spells to add:").append(spellCnt).append("\n");
|
||||
|
||||
@@ -5,6 +5,7 @@ import forge.card.CardEdition;
|
||||
import forge.card.CardRarity;
|
||||
import forge.card.CardRules;
|
||||
import forge.card.ColorSet;
|
||||
import forge.util.MyRandom;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -13,8 +14,9 @@ import java.util.Locale;
|
||||
public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
||||
private String name;
|
||||
private CardEdition edition;
|
||||
private String imageFileName;
|
||||
private ArrayList<String> imageFileName = new ArrayList<>();
|
||||
private CardRules card;
|
||||
private int artIndex = 1;
|
||||
|
||||
// takes a string of the form "<colors> <power> <toughness> <name>" such as: "B 0 0 Germ"
|
||||
public static String makeTokenFileName(String in) {
|
||||
@@ -100,23 +102,28 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
||||
build.add(edition.getCode());
|
||||
}
|
||||
|
||||
// Should future image file names be all lower case? Instead of Up case sets?
|
||||
return StringUtils.join(build, "_").toLowerCase();
|
||||
return StringUtils.join(build, "_").replace('*', 'x').toLowerCase();
|
||||
}
|
||||
|
||||
public PaperToken(final CardRules c) { this(c, null, null); }
|
||||
public PaperToken(final CardRules c, final String fileName) { this(c, null, fileName); }
|
||||
public PaperToken(final CardRules c, CardEdition edition) { this(c, edition, null); }
|
||||
public PaperToken(final CardRules c, CardEdition edition0, String imageFileName) {
|
||||
this.card = c;
|
||||
this.name = c.getName();
|
||||
this.edition = edition0;
|
||||
|
||||
if (edition != null && edition.getTokens().containsKey(imageFileName)) {
|
||||
this.artIndex = edition.getTokens().get(imageFileName);
|
||||
}
|
||||
|
||||
if (imageFileName == null) {
|
||||
this.imageFileName = makeTokenFileName(c, edition0);
|
||||
// This shouldn't really happen. We can just use the normalized name again for the base image name
|
||||
this.imageFileName.add(makeTokenFileName(c, edition0));
|
||||
} else {
|
||||
String formatEdition = null == edition || CardEdition.UNKNOWN == edition ? "" : edition.getCode();
|
||||
this.imageFileName = String.format("%s%s", formatEdition, imageFileName);
|
||||
String formatEdition = null == edition || CardEdition.UNKNOWN == edition ? "" : "_" + edition.getCode().toLowerCase();
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,14 +131,14 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
||||
|
||||
@Override public String toString() { return name; }
|
||||
@Override public String getEdition() { return edition != null ? edition.getCode() : "???"; }
|
||||
@Override public int getArtIndex() { return 0; } // This might change however
|
||||
@Override public int getArtIndex() { return artIndex; }
|
||||
@Override public boolean isFoil() { return false; }
|
||||
@Override public CardRules getRules() { return card; }
|
||||
|
||||
@Override public CardRarity getRarity() { return CardRarity.None; }
|
||||
|
||||
// Unfortunately this is a property of token, cannot move it outside of class
|
||||
public String getImageFilename() { return imageFileName; }
|
||||
public String getImageFilename() { return imageFileName.get(0); }
|
||||
|
||||
@Override public String getItemType() { return "Token"; }
|
||||
|
||||
@@ -139,6 +146,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard {
|
||||
|
||||
@Override
|
||||
public String getImageKey(boolean altState) {
|
||||
return ImageKeys.TOKEN_PREFIX + imageFileName.replace(" ", "_");
|
||||
int idx = MyRandom.getRandom().nextInt(artIndex);
|
||||
return ImageKeys.TOKEN_PREFIX + imageFileName.get(idx).replace(" ", "_");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package forge.token;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Maps;
|
||||
import forge.card.*;
|
||||
import forge.card.CardDb;
|
||||
import forge.card.CardEdition;
|
||||
import forge.card.CardRules;
|
||||
import forge.item.PaperToken;
|
||||
|
||||
import java.util.*;
|
||||
@@ -37,13 +39,25 @@ public class TokenDb implements ITokenDatabase {
|
||||
return getToken(tokenName, CardEdition.UNKNOWN.getName());
|
||||
}
|
||||
|
||||
public void preloadTokens() {
|
||||
for(CardEdition edition : this.editions) {
|
||||
for (String name : edition.getTokens().keySet()) {
|
||||
try {
|
||||
getToken(name, edition.getCode());
|
||||
} catch(Exception e) {
|
||||
System.out.println(name + "_" + edition.getCode() + " defined in Edition file, but not defined as a token script.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaperToken getToken(String tokenName, String edition) {
|
||||
String fullName = String.format("%s_%s", tokenName, edition.toLowerCase());
|
||||
|
||||
if (!tokensByName.containsKey(fullName)) {
|
||||
try {
|
||||
PaperToken pt = new PaperToken(rulesByName.get(tokenName), editions.get(edition));
|
||||
PaperToken pt = new PaperToken(rulesByName.get(tokenName), editions.get(edition), tokenName);
|
||||
tokensByName.put(fullName, pt);
|
||||
return pt;
|
||||
} catch(Exception e) {
|
||||
@@ -101,7 +115,7 @@ public class TokenDb implements ITokenDatabase {
|
||||
|
||||
@Override
|
||||
public List<PaperToken> getAllTokens() {
|
||||
return null;
|
||||
return new ArrayList<>(tokensByName.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,6 +135,6 @@ public class TokenDb implements ITokenDatabase {
|
||||
|
||||
@Override
|
||||
public Iterator<PaperToken> iterator() {
|
||||
return null;
|
||||
return tokensByName.values().iterator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ public class Localizer {
|
||||
MessageFormat formatter = null;
|
||||
|
||||
try {
|
||||
formatter = new MessageFormat(resourceBundle.getString(key.toLowerCase()), locale);
|
||||
//formatter = new MessageFormat(resourceBundle.getString(key.toLowerCase()), locale);
|
||||
formatter = new MessageFormat(resourceBundle.getString(key.toString()), locale);
|
||||
} catch (final IllegalArgumentException | MissingResourceException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -86,7 +87,7 @@ public class Localizer {
|
||||
resourceBundle = ResourceBundle.getBundle(languageRegionID, new Locale(splitLocale[0], splitLocale[1]), loader);
|
||||
} catch (NullPointerException | MissingResourceException e) {
|
||||
//If the language can't be loaded, default to US English
|
||||
resourceBundle = ResourceBundle.getBundle("en-GB", new Locale("en", "GB"), loader);
|
||||
resourceBundle = ResourceBundle.getBundle("en-US", new Locale("en", "US"), loader);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="src" path="/forge-core"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>forge-game</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
@@ -1,4 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=ISO-8859-1
|
||||
encoding//src/test/java=ISO-8859-1
|
||||
encoding/<project>=UTF-8
|
||||
@@ -1,5 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.source=1.7
|
||||
@@ -1,4 +0,0 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.20-SNAPSHOT</version>
|
||||
<version>1.6.23</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
@@ -29,6 +29,7 @@ import forge.game.ability.effects.AttachEffect;
|
||||
import forge.game.card.*;
|
||||
import forge.game.event.*;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.keyword.KeywordsChange;
|
||||
import forge.game.player.GameLossReason;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
@@ -50,9 +51,9 @@ import forge.util.collect.FCollection;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import forge.util.maps.HashMapOfLists;
|
||||
import forge.util.maps.MapOfLists;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Methods for common actions performed during a game.
|
||||
@@ -70,10 +71,8 @@ public class GameAction {
|
||||
}
|
||||
|
||||
public final void resetActivationsPerTurn() {
|
||||
final CardCollectionView all = game.getCardsInGame();
|
||||
|
||||
// Reset Activations per Turn
|
||||
for (final Card card : all) {
|
||||
for (final Card card : game.getCardsInGame()) {
|
||||
for (final SpellAbility sa : card.getAllSpellAbilities()) {
|
||||
sa.getRestrictions().resetTurnActivations();
|
||||
}
|
||||
@@ -104,6 +103,7 @@ public class GameAction {
|
||||
boolean toBattlefield = zoneTo.is(ZoneType.Battlefield);
|
||||
boolean fromBattlefield = zoneFrom != null && zoneFrom.is(ZoneType.Battlefield);
|
||||
boolean toHand = zoneTo.is(ZoneType.Hand);
|
||||
boolean wasFacedown = c.isFaceDown();
|
||||
|
||||
//Rule 110.5g: A token that has left the battlefield can't move to another zone
|
||||
if (c.isToken() && zoneFrom != null && !fromBattlefield && !zoneFrom.is(ZoneType.Command)) {
|
||||
@@ -147,16 +147,11 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
// Cards returned from exile face-down must be reset to their original state, otherwise
|
||||
// all sort of funky shenanigans may happen later (e.g. their ETB replacement effects are set
|
||||
// up on the wrong card state etc.).
|
||||
if (c.isFaceDown() && (fromBattlefield || (toHand && zoneFrom.is(ZoneType.Exile)))) {
|
||||
c.setState(CardStateName.Original, true);
|
||||
c.runFaceupCommands();
|
||||
}
|
||||
|
||||
// Clean up the temporary Dash SVar when the Dashed card leaves the battlefield
|
||||
if (fromBattlefield && c.getSVar("EndOfTurnLeavePlay").equals("Dash")) {
|
||||
// Clean up the temporary AtEOT SVar
|
||||
String endofTurn = c.getSVar("EndOfTurnLeavePlay");
|
||||
if (fromBattlefield && (endofTurn.equals("Dash") || endofTurn.equals("AtEOT"))) {
|
||||
c.removeSVar("EndOfTurnLeavePlay");
|
||||
}
|
||||
|
||||
@@ -189,6 +184,14 @@ public class GameAction {
|
||||
lastKnownInfo = CardUtil.getLKICopy(c);
|
||||
}
|
||||
|
||||
// Cards returned from exile face-down must be reset to their original state, otherwise
|
||||
// all sort of funky shenanigans may happen later (e.g. their ETB replacement effects are set
|
||||
// up on the wrong card state etc.).
|
||||
if (wasFacedown && (fromBattlefield || (toHand && zoneFrom.is(ZoneType.Exile)))) {
|
||||
c.setState(CardStateName.Original, true);
|
||||
c.runFaceupCommands();
|
||||
}
|
||||
|
||||
if (!c.isToken()) {
|
||||
if (c.isCloned()) {
|
||||
c.switchStates(CardStateName.Original, CardStateName.Cloner, false);
|
||||
@@ -213,15 +216,15 @@ public class GameAction {
|
||||
c.updateStateForView();
|
||||
}
|
||||
|
||||
if (fromBattlefield && c.getCurrentStateName() != CardStateName.Original) {
|
||||
copied = CardFactory.copyCard(c, false);
|
||||
|
||||
if (fromBattlefield && copied.getCurrentStateName() != CardStateName.Original) {
|
||||
// when a card leaves the battlefield, ensure it's in its original state
|
||||
// (we need to do this on the object before copying it, or it won't work correctly e.g.
|
||||
// on Transformed objects)
|
||||
c.setState(CardStateName.Original, false);
|
||||
copied.setState(CardStateName.Original, false);
|
||||
}
|
||||
|
||||
copied = CardFactory.copyCard(c, false);
|
||||
|
||||
copied.setUnearthed(c.isUnearthed());
|
||||
copied.setTapped(false);
|
||||
|
||||
@@ -248,26 +251,25 @@ public class GameAction {
|
||||
// special rule for Worms of the Earth
|
||||
if (toBattlefield && game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLandBattlefield)) {
|
||||
// something that is already a Land cant enter the battlefield
|
||||
if (c.isLand()) {
|
||||
return c;
|
||||
}
|
||||
// check if something would be a land
|
||||
Card noLandLKI = CardUtil.getLKICopy(c);
|
||||
// this check needs to check if this card would be on the battlefield
|
||||
noLandLKI.setLastKnownZone(zoneTo);
|
||||
Card noLandLKI = c;
|
||||
if (!c.isLand()) {
|
||||
// check if something would be a land
|
||||
noLandLKI = CardUtil.getLKICopy(c);
|
||||
// this check needs to check if this card would be on the battlefield
|
||||
noLandLKI.setLastKnownZone(zoneTo);
|
||||
|
||||
CardCollection preList = new CardCollection(noLandLKI);
|
||||
checkStaticAbilities(false, Sets.newHashSet(noLandLKI), preList);
|
||||
CardCollection preList = new CardCollection(noLandLKI);
|
||||
checkStaticAbilities(false, Sets.newHashSet(noLandLKI), preList);
|
||||
|
||||
// fake etb counters thing, then if something changed,
|
||||
// need to apply checkStaticAbilities again
|
||||
if(!noLandLKI.isLand()) {
|
||||
if (noLandLKI.putEtbCounters()) {
|
||||
// counters are added need to check again
|
||||
checkStaticAbilities(false, Sets.newHashSet(noLandLKI), preList);
|
||||
// fake etb counters thing, then if something changed,
|
||||
// need to apply checkStaticAbilities again
|
||||
if(!noLandLKI.isLand()) {
|
||||
if (noLandLKI.putEtbCounters()) {
|
||||
// counters are added need to check again
|
||||
checkStaticAbilities(false, Sets.newHashSet(noLandLKI), preList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(noLandLKI.isLand()) {
|
||||
// if it isn't on the Stack, it stays in that Zone
|
||||
if (!c.getZone().is(ZoneType.Stack)) {
|
||||
@@ -276,6 +278,7 @@ public class GameAction {
|
||||
// if something would only be a land when entering the battlefield and not before
|
||||
// put it into the graveyard instead
|
||||
zoneTo = c.getOwner().getZone(ZoneType.Graveyard);
|
||||
|
||||
// reset facedown
|
||||
copied.setState(CardStateName.Original, false);
|
||||
copied.setManifested(false);
|
||||
@@ -283,6 +286,26 @@ public class GameAction {
|
||||
|
||||
// not to battlefield anymore!
|
||||
toBattlefield = false;
|
||||
|
||||
if (copied.isCloned()) {
|
||||
copied.switchStates(CardStateName.Original, CardStateName.Cloner, false);
|
||||
copied.setState(CardStateName.Original, false);
|
||||
copied.clearStates(CardStateName.Cloner, false);
|
||||
if (copied.isFlipCard()) {
|
||||
copied.clearStates(CardStateName.Flipped, false);
|
||||
}
|
||||
if (copied.getStates().contains(CardStateName.OriginalText)) {
|
||||
copied.clearStates(CardStateName.OriginalText, false);
|
||||
copied.removeSVar("GainingTextFrom");
|
||||
copied.removeSVar("GainingTextFromTimestamp");
|
||||
}
|
||||
}
|
||||
|
||||
if (copied.getCurrentStateName() != CardStateName.Original) {
|
||||
copied.setState(CardStateName.Original, false);
|
||||
}
|
||||
|
||||
copied.updateStateForView();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,6 +314,33 @@ public class GameAction {
|
||||
copied.getOwner().addInboundToken(copied);
|
||||
}
|
||||
|
||||
if (toBattlefield) {
|
||||
// HACK for making the RIOT enchantment look into the Future
|
||||
// need to check the Keywords what it would have on the Battlefield
|
||||
Card riotLKI = CardUtil.getLKICopy(copied);
|
||||
riotLKI.setLastKnownZone(zoneTo);
|
||||
CardCollection preList = new CardCollection(riotLKI);
|
||||
checkStaticAbilities(false, Sets.newHashSet(riotLKI), preList);
|
||||
|
||||
List<Long> changedTimeStamps = Lists.newArrayList();
|
||||
for(Map.Entry<Long, KeywordsChange> e : riotLKI.getChangedCardKeywords().entrySet()) {
|
||||
if (!copied.hasChangedCardKeywords(e.getKey())) {
|
||||
KeywordsChange o = e.getValue();
|
||||
o.setHostCard(copied);
|
||||
for (KeywordInterface k : o.getKeywords()) {
|
||||
for (ReplacementEffect re : k.getReplacements()) {
|
||||
// this param need to be set, otherwise in ReplaceMoved it fails
|
||||
re.getMapParams().put("BypassEtbCheck", "True");
|
||||
}
|
||||
}
|
||||
copied.addChangedCardKeywordsInternal(o, e.getKey());
|
||||
changedTimeStamps.add(e.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
checkStaticAbilities(false);
|
||||
}
|
||||
|
||||
Map<String, Object> repParams = Maps.newHashMap();
|
||||
repParams.put("Event", "Moved");
|
||||
repParams.put("Affected", copied);
|
||||
@@ -373,7 +423,7 @@ public class GameAction {
|
||||
// Need to apply any static effects to produce correct triggers
|
||||
checkStaticAbilities();
|
||||
game.getTriggerHandler().clearInstrinsicActiveTriggers(c, zoneFrom);
|
||||
game.getTriggerHandler().registerActiveTrigger(c, false);
|
||||
game.getTriggerHandler().registerActiveTrigger(lastKnownInfo, false);
|
||||
|
||||
// do ETB counters after StaticAbilities check
|
||||
if (!suppress) {
|
||||
@@ -435,10 +485,14 @@ public class GameAction {
|
||||
}
|
||||
|
||||
// rule 504.6: reveal a face-down card leaving the stack
|
||||
if (zoneFrom != null && zoneTo != null && zoneFrom.is(ZoneType.Stack) && !zoneTo.is(ZoneType.Battlefield) && c.isFaceDown()) {
|
||||
if (zoneFrom != null && zoneTo != null && zoneFrom.is(ZoneType.Stack) && !zoneTo.is(ZoneType.Battlefield) && wasFacedown) {
|
||||
// FIXME: tracker freeze-unfreeze is needed here to avoid a bug with the card staying face down in the View for the reveal
|
||||
boolean trackerFrozen = game.getTracker().isFrozen();
|
||||
game.getTracker().unfreeze();
|
||||
c.setState(CardStateName.Original, true);
|
||||
reveal(new CardCollection(c), c.getOwner(), true, "Face-down card moves from the stack: ");
|
||||
c.setState(CardStateName.FaceDown, true);
|
||||
if (trackerFrozen) { game.getTracker().freeze(); }
|
||||
}
|
||||
|
||||
if (fromBattlefield) {
|
||||
@@ -466,19 +520,19 @@ public class GameAction {
|
||||
changeZone(null, zoneTo, unmeld, position, cause, params);
|
||||
}
|
||||
// Reveal if face-down
|
||||
if (c.isFaceDown()) {
|
||||
if (wasFacedown) {
|
||||
// FIXME: tracker freeze-unfreeze is needed here to avoid a bug with the card staying face down in the View for the reveal
|
||||
boolean trackerFrozen = game.getTracker().isFrozen();
|
||||
game.getTracker().unfreeze();
|
||||
c.setState(CardStateName.Original, true);
|
||||
reveal(new CardCollection(c), c.getOwner(), true, "Face-down card leaves the battlefield: ");
|
||||
c.setState(CardStateName.FaceDown, true);
|
||||
if (trackerFrozen) { game.getTracker().freeze(); }
|
||||
copied.setState(CardStateName.Original, true);
|
||||
}
|
||||
unattachCardLeavingBattlefield(copied);
|
||||
// Remove all changed keywords
|
||||
copied.removeAllChangedText(game.getNextTimestamp());
|
||||
// reset activations
|
||||
for (SpellAbility ab : copied.getSpellAbilities()) {
|
||||
ab.getRestrictions().resetTurnActivations();
|
||||
}
|
||||
} else if (toBattlefield) {
|
||||
// reset timestamp in changezone effects so they have same timestamp if ETB simutaneously
|
||||
copied.setTimestamp(game.getNextTimestamp());
|
||||
@@ -1094,7 +1148,7 @@ public class GameAction {
|
||||
|
||||
if (c.isAttachedToEntity()) {
|
||||
final GameEntity ge = c.getEntityAttachedTo();
|
||||
if (!ge.canBeAttached(c)) {
|
||||
if (!ge.canBeAttached(c, true)) {
|
||||
c.unattachFromEntity(ge);
|
||||
checkAgain = true;
|
||||
}
|
||||
@@ -1669,12 +1723,8 @@ public class GameAction {
|
||||
|
||||
// rule 103.4b
|
||||
boolean isMultiPlayer = game.getPlayers().size() > 2;
|
||||
int mulliganDelta = isMultiPlayer ? 0 : 1;
|
||||
|
||||
// https://magic.wizards.com/en/articles/archive/feature/checking-brawl-2018-07-09
|
||||
if (game.getRules().hasAppliedVariant(GameType.Brawl) && !isMultiPlayer){
|
||||
mulliganDelta = 0;
|
||||
}
|
||||
int mulliganDelta = isMultiPlayer || game.getRules().hasAppliedVariant(GameType.Brawl) ? 0 : 1;
|
||||
|
||||
boolean allKept;
|
||||
do {
|
||||
@@ -1690,32 +1740,17 @@ public class GameAction {
|
||||
}
|
||||
|
||||
if (toMulligan != null && !toMulligan.isEmpty()) {
|
||||
if (!isCommander) {
|
||||
toMulligan = new CardCollection(p.getCardsIn(ZoneType.Hand));
|
||||
for (final Card c : toMulligan) {
|
||||
moveToLibrary(c, null, null);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(100); //delay for a tiny bit to give UI a chance catch up
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
p.shuffle(null);
|
||||
p.drawCards(handSize[i] - mulliganDelta);
|
||||
} else {
|
||||
List<Card> toExile = Lists.newArrayList(toMulligan);
|
||||
for (Card c : toExile) {
|
||||
exile(c, null, null);
|
||||
}
|
||||
exiledDuringMulligans.addAll(p, toExile);
|
||||
try {
|
||||
Thread.sleep(100); //delay for a tiny bit to give UI a chance catch up
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
p.drawCards(toExile.size() - 1);
|
||||
toMulligan = new CardCollection(p.getCardsIn(ZoneType.Hand));
|
||||
for (final Card c : toMulligan) {
|
||||
moveToLibrary(c, null, null);
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(100); //delay for a tiny bit to give UI a chance catch up
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
p.shuffle(null);
|
||||
p.drawCards(handSize[i] - mulliganDelta);
|
||||
p.onMulliganned();
|
||||
allKept = false;
|
||||
} else {
|
||||
@@ -1726,21 +1761,17 @@ public class GameAction {
|
||||
mulliganDelta++;
|
||||
} while (!allKept);
|
||||
|
||||
if (isCommander) {
|
||||
for (Entry<Player, Collection<Card>> kv : exiledDuringMulligans.entrySet()) {
|
||||
Player p = kv.getKey();
|
||||
Collection<Card> cc = kv.getValue();
|
||||
for (Card c : cc) {
|
||||
moveToLibrary(c, null, null);
|
||||
}
|
||||
p.shuffle(null);
|
||||
//Vancouver Mulligan as a scry with the decisions inside
|
||||
List<Player> scryers = Lists.newArrayList();
|
||||
for(Player p : whoCanMulligan) {
|
||||
if (p.getStartingHandSize() > p.getZone(ZoneType.Hand).size()) {
|
||||
scryers.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
//Vancouver Mulligan
|
||||
for(Player p : whoCanMulligan) {
|
||||
if (p.getStartingHandSize() > p.getZone(ZoneType.Hand).size()) {
|
||||
p.scry(1, null);
|
||||
for(Player p : scryers) {
|
||||
if (p.getController().confirmMulliganScry(p)) {
|
||||
scry(ImmutableList.of(p), 1, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1838,4 +1869,68 @@ public class GameAction {
|
||||
runParams.put("Player", p);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.BecomeMonarch, runParams, false);
|
||||
}
|
||||
|
||||
// Make scry an action function so that it can be used for mulligans (with a null cause)
|
||||
// Assumes that the list of players is in APNAP order, which should be the case
|
||||
// Optional here as well to handle the way that mulligans do the choice
|
||||
// 701.17. Scry
|
||||
// 701.17a To "scry N" means to look at the top N cards of your library, then put any number of them
|
||||
// on the bottom of your library in any order and the rest on top of your library in any order.
|
||||
// 701.17b If a player is instructed to scry 0, no scry event occurs. Abilities that trigger whenever a
|
||||
// player scries won’t trigger.
|
||||
// 701.17c If multiple players scry at once, each of those players looks at the top cards of their library
|
||||
// at the same time. Those players decide in APNAP order (see rule 101.4) where to put those
|
||||
// cards, then those cards move at the same time.
|
||||
public void scry(List<Player> players, int numScry, SpellAbility cause) {
|
||||
if (numScry == 0) {
|
||||
return;
|
||||
}
|
||||
// reveal the top N library cards to the player (only)
|
||||
// no real need to separate out the look if
|
||||
// there is only one player scrying
|
||||
if (players.size() > 1) {
|
||||
for (final Player p : players) {
|
||||
final CardCollection topN = new CardCollection(p.getCardsIn(ZoneType.Library, numScry));
|
||||
revealTo(topN, p);
|
||||
}
|
||||
}
|
||||
// make the decisions
|
||||
List<ImmutablePair<CardCollection, CardCollection>> decisions = Lists.newArrayList();
|
||||
for (final Player p : players) {
|
||||
final CardCollection topN = new CardCollection(p.getCardsIn(ZoneType.Library, numScry));
|
||||
ImmutablePair<CardCollection, CardCollection> decision = p.getController().arrangeForScry(topN);
|
||||
decisions.add(decision);
|
||||
int numToTop = decision.getLeft() == null ? 0 : decision.getLeft().size();
|
||||
int numToBottom = decision.getRight() == null ? 0 : decision.getRight().size();
|
||||
|
||||
// publicize the decision
|
||||
game.fireEvent(new GameEventScry(p, numToTop, numToBottom));
|
||||
}
|
||||
// do the moves after all the decisions (maybe not necesssary, but let's
|
||||
// do it the official way)
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
// no good iterate simultaneously in Java
|
||||
final Player p = players.get(i);
|
||||
final CardCollection toTop = decisions.get(i).getLeft();
|
||||
final CardCollection toBottom = decisions.get(i).getRight();
|
||||
if (toTop != null) {
|
||||
Collections.reverse(toTop); // reverse to get the correct order
|
||||
for (Card c : toTop) {
|
||||
moveToLibrary(c, cause, null);
|
||||
}
|
||||
}
|
||||
if (toBottom != null) {
|
||||
for (Card c : toBottom) {
|
||||
moveToBottomOfLibrary(c, cause, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (cause != null) {
|
||||
// set up triggers (but not actually do them until later)
|
||||
final Map<String, Object> runParams = Maps.newHashMap();
|
||||
runParams.put("Player", p);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Scry, runParams, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,8 @@ public final class GameActionUtil {
|
||||
}
|
||||
|
||||
if (lkicheck) {
|
||||
// double freeze tracker, so it doesn't update view
|
||||
game.getTracker().freeze();
|
||||
CardCollection preList = new CardCollection(source);
|
||||
game.getAction().checkStaticAbilities(false, Sets.newHashSet(source), preList);
|
||||
}
|
||||
@@ -164,6 +166,10 @@ public final class GameActionUtil {
|
||||
// reset static abilities
|
||||
if (lkicheck) {
|
||||
game.getAction().checkStaticAbilities(false);
|
||||
// clear delayed changes, this check should not have updated the view
|
||||
game.getTracker().clearDelayed();
|
||||
// need to unfreeze tracker
|
||||
game.getTracker().unfreeze();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ import forge.game.cost.Cost;
|
||||
import forge.game.spellability.*;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.FileSection;
|
||||
import io.sentry.Sentry;
|
||||
import io.sentry.event.BreadcrumbBuilder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -130,7 +132,16 @@ public final class AbilityFactory {
|
||||
String source = state.getName().isEmpty() ? abString : state.getName();
|
||||
throw new RuntimeException("AbilityFactory : getAbility -- no API in " + source + ": " + abString);
|
||||
}
|
||||
return getAbility(mapParams, type, state, parent);
|
||||
try {
|
||||
return getAbility(mapParams, type, state, parent);
|
||||
} catch (Error | Exception ex) {
|
||||
String msg = "AbilityFactory:getAbility: crash when trying to create ability ";
|
||||
Sentry.getContext().recordBreadcrumb(
|
||||
new BreadcrumbBuilder().setMessage(msg)
|
||||
.withData("Card", state.getName()).withData("Ability", abString).build()
|
||||
);
|
||||
throw new RuntimeException(msg + " of card: " + state.getName(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static final SpellAbility getAbility(final Card hostCard, final String svar) {
|
||||
|
||||
@@ -225,10 +225,6 @@ public class AbilityUtils {
|
||||
if (o != null && o instanceof Card) {
|
||||
cards.add(game.getCardState((Card) o));
|
||||
}
|
||||
} else if (defined.equals("Clones")) {
|
||||
for (final Card clone : hostCard.getClones()) {
|
||||
cards.add(game.getCardState(clone));
|
||||
}
|
||||
} else if (defined.equals("Imprinted")) {
|
||||
for (final Card imprint : hostCard.getImprintedCards()) {
|
||||
cards.add(game.getCardState(imprint));
|
||||
|
||||
@@ -173,8 +173,7 @@ public abstract class SpellAbilityEffect {
|
||||
protected final static CardCollection getDefinedCardsOrTargeted(final SpellAbility sa, final String definedParam) { return getCards(true, definedParam, sa); }
|
||||
|
||||
private static CardCollection getCards(final boolean definedFirst, final String definedParam, final SpellAbility sa) {
|
||||
final boolean useTargets = sa.usesTargeting() && (!definedFirst || !sa.hasParam(definedParam))
|
||||
&& sa.getTargets() != null && (sa.getTargets().isTargetingAnyCard() || sa.getTargets().getTargets().isEmpty());
|
||||
final boolean useTargets = sa.usesTargeting() && (!definedFirst || !sa.hasParam(definedParam));
|
||||
return useTargets ? new CardCollection(sa.getTargets().getTargetCards())
|
||||
: AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam(definedParam), sa);
|
||||
}
|
||||
@@ -230,8 +229,17 @@ public abstract class SpellAbilityEffect {
|
||||
|
||||
if (desc.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(location).append(" ");
|
||||
if (location.equals("Hand")) {
|
||||
sb.append("Return ");
|
||||
} else if (location.equals("SacrificeCtrl")) {
|
||||
sb.append("Its controller sacrifices ");
|
||||
} else {
|
||||
sb.append(location).append(" ");
|
||||
}
|
||||
sb.append(Lang.joinHomogenous(crds));
|
||||
if (location.equals("Hand")) {
|
||||
sb.append("to your hand").append(" ");
|
||||
}
|
||||
sb.append(" at the ");
|
||||
if (combat) {
|
||||
sb.append("end of combat.");
|
||||
@@ -255,9 +263,18 @@ public abstract class SpellAbilityEffect {
|
||||
final Trigger trig = TriggerHandler.parseTrigger(delTrig.toString(), sa.getHostCard(), intrinsic);
|
||||
for (final Card c : crds) {
|
||||
trig.addRemembered(c);
|
||||
|
||||
// Svar for AI
|
||||
if (!c.hasSVar("EndOfTurnLeavePlay")) {
|
||||
c.setSVar("EndOfTurnLeavePlay", "AtEOT");
|
||||
}
|
||||
}
|
||||
String trigSA = "";
|
||||
if (location.equals("Sacrifice")) {
|
||||
if (location.equals("Hand")) {
|
||||
trigSA = "DB$ ChangeZone | Defined$ DelayTriggerRemembered | Origin$ Battlefield | Destination$ Hand";
|
||||
} else if (location.equals("SacrificeCtrl")) {
|
||||
trigSA = "DB$ SacrificeAll | Defined$ DelayTriggerRemembered";
|
||||
} else if (location.equals("Sacrifice")) {
|
||||
trigSA = "DB$ SacrificeAll | Defined$ DelayTriggerRemembered | Controller$ You";
|
||||
} else if (location.equals("Exile")) {
|
||||
trigSA = "DB$ ChangeZone | Defined$ DelayTriggerRemembered | Origin$ Battlefield | Destination$ Exile";
|
||||
@@ -289,6 +306,11 @@ public abstract class SpellAbilityEffect {
|
||||
}
|
||||
trig.setOverridingAbility(AbilityFactory.getAbility(trigSA, card));
|
||||
card.addTrigger(trig);
|
||||
|
||||
// Svar for AI
|
||||
if (!card.hasSVar("EndOfTurnLeavePlay")) {
|
||||
card.setSVar("EndOfTurnLeavePlay", "AtEOT");
|
||||
}
|
||||
}
|
||||
|
||||
protected static void addForgetOnMovedTrigger(final Card card, final String zone) {
|
||||
|
||||
@@ -10,9 +10,11 @@ import forge.game.trigger.TriggerType;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class BecomesBlockedEffect extends SpellAbilityEffect {
|
||||
|
||||
@@ -38,10 +40,11 @@ public class BecomesBlockedEffect extends SpellAbilityEffect {
|
||||
game.getCombat().setBlocked(c, true);
|
||||
if (!c.getDamageHistory().getCreatureGotBlockedThisCombat()) {
|
||||
isCombatChanged = true;
|
||||
final HashMap<String, Object> runParams = new HashMap<String, Object>();
|
||||
final Map<String, Object> runParams = Maps.newHashMap();
|
||||
runParams.put("Attacker", c);
|
||||
runParams.put("Blockers", new ArrayList<Card>());
|
||||
runParams.put("Blockers", Lists.<Card>newArrayList());
|
||||
runParams.put("NumBlockers", 0);
|
||||
runParams.put("Defender", game.getCombat().getDefenderByAttacker(c));
|
||||
runParams.put("DefendingPlayer", game.getCombat().getDefenderPlayerByAttacker(c));
|
||||
game.getTriggerHandler().runTrigger(TriggerType.AttackerBlocked, runParams, false);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
|
||||
newTargetBlock.remove(oldTarget);
|
||||
replaceIn.updateTarget(newTargetBlock);
|
||||
// 3. test if updated choices would be correct.
|
||||
GameObject newTarget = Iterables.getFirst(getDefinedCardsOrTargeted(sa), null);
|
||||
GameObject newTarget = Iterables.getFirst(getDefinedCardsOrTargeted(sa, "DefinedMagnet"), null);
|
||||
|
||||
if (replaceIn.getSpellAbility(true).canTarget(newTarget)) {
|
||||
newTargetBlock.add(newTarget);
|
||||
|
||||
@@ -377,8 +377,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
*/
|
||||
private void changeKnownOriginResolve(final SpellAbility sa) {
|
||||
final boolean onlySpells = sa.hasParam("OnlySpells");
|
||||
Iterable<Card> tgtCards = !onlySpells ? getTargetCards(sa) : new CardCollection();
|
||||
Iterable<Card> tgtCards = getTargetCards(sa);
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Player player = sa.getActivatingPlayer();
|
||||
final Card hostCard = sa.getHostCard();
|
||||
@@ -400,7 +399,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
altDest = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final CardZoneTable triggerList = new CardZoneTable();
|
||||
// changing zones for spells on the stack
|
||||
for (final SpellAbility tgtSA : getTargetSpells(sa)) {
|
||||
if (!tgtSA.isSpell()) { // Catch any abilities or triggers that slip through somehow
|
||||
@@ -412,7 +412,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
|
||||
removeFromStack(tgtSA, sa, si, game);
|
||||
removeFromStack(tgtSA, sa, si, game, triggerList);
|
||||
} // End of change from stack
|
||||
|
||||
final String remember = sa.getParam("RememberChanged");
|
||||
@@ -429,7 +429,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
|
||||
final boolean optional = sa.hasParam("Optional");
|
||||
final long ts = game.getNextTimestamp();
|
||||
final CardZoneTable triggerList = new CardZoneTable();
|
||||
|
||||
for (final Card tgtC : tgtCards) {
|
||||
if (tgt != null && tgtC.isInPlay() && !tgtC.canBeTargetedBy(sa)) {
|
||||
@@ -606,6 +605,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
|
||||
if (remember != null) {
|
||||
hostCard.addRemembered(movedCard);
|
||||
// addRememberedFromCardState ?
|
||||
if (tgtC.getMeldedWith() != null) {
|
||||
Card meld = game.getCardState(tgtC.getMeldedWith(), null);
|
||||
if (meld != null) {
|
||||
hostCard.addRemembered(meld);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (forget != null) {
|
||||
hostCard.removeRemembered(movedCard);
|
||||
@@ -852,7 +858,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
// ensure that selection is within maximum allowed changeNum
|
||||
do {
|
||||
selectedCards = decider.getController().chooseCardsForZoneChange(destination, origin, sa, fetchList, delayedReveal, selectPrompt, decider);
|
||||
selectedCards = decider.getController().chooseCardsForZoneChange(destination, origin, sa, fetchList, 0, changeNum, delayedReveal, selectPrompt, decider);
|
||||
} while (selectedCards != null && selectedCards.size() > changeNum);
|
||||
if (selectedCards != null) {
|
||||
for (Card card : selectedCards) {
|
||||
@@ -1121,6 +1127,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
|
||||
if (remember) {
|
||||
source.addRemembered(movedCard);
|
||||
// addRememberedFromCardState ?
|
||||
if (c.getMeldedWith() != null) {
|
||||
Card meld = game.getCardState(c.getMeldedWith(), null);
|
||||
if (meld != null) {
|
||||
source.addRemembered(meld);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (forget) {
|
||||
source.removeRemembered(movedCard);
|
||||
@@ -1169,36 +1182,39 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
* object.
|
||||
* @param game
|
||||
*/
|
||||
private static void removeFromStack(final SpellAbility tgtSA, final SpellAbility srcSA, final SpellAbilityStackInstance si, final Game game) {
|
||||
private static void removeFromStack(final SpellAbility tgtSA, final SpellAbility srcSA, final SpellAbilityStackInstance si, final Game game, CardZoneTable triggerList) {
|
||||
final Card tgtHost = tgtSA.getHostCard();
|
||||
final Zone originZone = tgtHost.getZone();
|
||||
game.getStack().remove(si);
|
||||
|
||||
Map<String,Object> params = Maps.newHashMap();
|
||||
params.put("StackSa", tgtSA);
|
||||
params.put("StackSi", si);
|
||||
|
||||
Card movedCard = null;
|
||||
if (srcSA.hasParam("Destination")) {
|
||||
final boolean remember = srcSA.hasParam("RememberChanged");
|
||||
if (tgtSA.isAbility()) {
|
||||
// Shouldn't be able to target Abilities but leaving this in for now
|
||||
} else if (srcSA.getParam("Destination").equals("Graveyard")) {
|
||||
game.getAction().moveToGraveyard(tgtSA.getHostCard(), srcSA, params);
|
||||
movedCard = game.getAction().moveToGraveyard(tgtHost, srcSA, params);
|
||||
} else if (srcSA.getParam("Destination").equals("Exile")) {
|
||||
Card host = srcSA.getOriginalHost();
|
||||
if (host == null) {
|
||||
host = srcSA.getHostCard();
|
||||
}
|
||||
tgtSA.getHostCard().setExiledWith(host);
|
||||
game.getAction().exile(tgtSA.getHostCard(), srcSA, params);
|
||||
movedCard = game.getAction().exile(tgtHost, srcSA, params);
|
||||
movedCard.setExiledWith(host);
|
||||
} else if (srcSA.getParam("Destination").equals("TopOfLibrary")) {
|
||||
game.getAction().moveToLibrary(tgtSA.getHostCard(), srcSA, params);
|
||||
movedCard = game.getAction().moveToLibrary(tgtHost, srcSA, params);
|
||||
} else if (srcSA.getParam("Destination").equals("Hand")) {
|
||||
game.getAction().moveToHand(tgtSA.getHostCard(), srcSA, params);
|
||||
movedCard = game.getAction().moveToHand(tgtHost, srcSA, params);
|
||||
} else if (srcSA.getParam("Destination").equals("BottomOfLibrary")) {
|
||||
game.getAction().moveToBottomOfLibrary(tgtSA.getHostCard(), srcSA, params);
|
||||
movedCard = game.getAction().moveToBottomOfLibrary(tgtHost, srcSA, params);
|
||||
} else if (srcSA.getParam("Destination").equals("Library")) {
|
||||
game.getAction().moveToBottomOfLibrary(tgtSA.getHostCard(), srcSA, params);
|
||||
movedCard = game.getAction().moveToBottomOfLibrary(tgtHost, srcSA, params);
|
||||
if (srcSA.hasParam("Shuffle") && "True".equals(srcSA.getParam("Shuffle"))) {
|
||||
tgtSA.getHostCard().getOwner().shuffle(srcSA);
|
||||
tgtHost.getOwner().shuffle(srcSA);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("AbilityFactory_ChangeZone: Invalid Destination argument for card "
|
||||
@@ -1206,12 +1222,16 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (remember) {
|
||||
srcSA.getHostCard().addRemembered(tgtSA.getHostCard());
|
||||
srcSA.getHostCard().addRemembered(tgtHost);
|
||||
// TODO or remember moved?
|
||||
}
|
||||
|
||||
if (!tgtSA.isAbility()) {
|
||||
System.out.println("Moving spell to " + srcSA.getParam("Destination"));
|
||||
}
|
||||
if (originZone != null && movedCard != null) {
|
||||
triggerList.put(originZone.getZoneType(), movedCard.getZone().getZoneType(), movedCard);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,8 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
|
||||
if (num == min) {
|
||||
sb.append(Lang.getNumeral(num));
|
||||
} else if (min == 0) {
|
||||
sb.append("up to ").append(Lang.getNumeral(num));
|
||||
} else {
|
||||
sb.append(Lang.getNumeral(min)).append(" or ").append(list.size() == 2 ? "both" : "more");
|
||||
}
|
||||
@@ -101,6 +103,8 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
|
||||
if (num == min) {
|
||||
sb.append(Lang.getNumeral(num));
|
||||
} else if (min == 0) {
|
||||
sb.append("up to ").append(Lang.getNumeral(num));
|
||||
} else {
|
||||
sb.append(Lang.getNumeral(min)).append(" or ").append(list.size() == 2 ? "both" : "more");
|
||||
}
|
||||
@@ -146,19 +150,19 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
return "";
|
||||
}
|
||||
|
||||
public static void makeChoices(SpellAbility sa) {
|
||||
public static boolean makeChoices(SpellAbility sa) {
|
||||
//this resets all previous choices
|
||||
sa.setSubAbility(null);
|
||||
|
||||
// Entwine does use all Choices
|
||||
if (sa.isEntwine()) {
|
||||
chainAbilities(sa, makePossibleOptions(sa));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
final int num = sa.hasParam("CharmNumOnResolve") ?
|
||||
AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CharmNumOnResolve"), sa)
|
||||
: Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
|
||||
: Integer.parseInt(sa.getParamOrDefault("CharmNum", "1"));
|
||||
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
|
||||
|
||||
Card source = sa.getHostCard();
|
||||
@@ -177,6 +181,7 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
|
||||
List<AbilitySub> chosen = chooser.getController().chooseModeForAbility(sa, min, num, sa.hasParam("CanRepeatModes"));
|
||||
chainAbilities(sa, chosen);
|
||||
return chosen != null && !chosen.isEmpty();
|
||||
}
|
||||
|
||||
private static void chainAbilities(SpellAbility sa, List<AbilitySub> chosen) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.game.GameAction;
|
||||
import forge.game.GameLogEntryType;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
@@ -59,9 +60,7 @@ public class ClashEffect extends SpellAbilityEffect {
|
||||
* <p>
|
||||
* clashWithOpponent.
|
||||
* </p>
|
||||
*
|
||||
* @param source
|
||||
* a {@link forge.game.card.Card} object.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
private static boolean clashWithOpponent(final SpellAbility sa) {
|
||||
@@ -126,11 +125,14 @@ public class ClashEffect extends SpellAbilityEffect {
|
||||
private static void clashMoveToTopOrBottom(final Player p, final Card c, final SpellAbility sa) {
|
||||
final GameAction action = p.getGame().getAction();
|
||||
final boolean putOnTop = p.getController().willPutCardOnTop(c);
|
||||
final String location = putOnTop ? "top" : "bottom";
|
||||
final String clashOutcome = p.getName() + " clashed and put " + c.getName() + " to the " + location + " of library.";
|
||||
|
||||
if (putOnTop) {
|
||||
action.moveToLibrary(c, sa);
|
||||
} else {
|
||||
action.moveToBottomOfLibrary(c, sa);
|
||||
}
|
||||
// computer just puts the card back until such time it can make a smarter decision
|
||||
p.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, clashOutcome);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
tgtCard.clearImprintedCards();
|
||||
|
||||
// check if clone is now an Aura that needs to be attached
|
||||
if (tgtCard.isAura()) {
|
||||
if (tgtCard.isAura() && !tgtCard.getZone().is(ZoneType.Battlefield)) {
|
||||
AttachEffect.attachAuraOnIndirectEnterBattlefield(tgtCard);
|
||||
}
|
||||
|
||||
|
||||
@@ -164,7 +164,6 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
|
||||
} else {
|
||||
tgtCards = getTargetCards(sa);
|
||||
}
|
||||
host.clearClones();
|
||||
|
||||
for (final Card c : tgtCards) {
|
||||
if (!sa.usesTargeting() || c.canBeTargetedBy(sa)) {
|
||||
@@ -184,7 +183,6 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
|
||||
//copyInPlay.setSetCode(c.getSetCode());
|
||||
|
||||
copyInPlay.setCloneOrigin(host);
|
||||
sa.getHostCard().addClone(copyInPlay);
|
||||
if (!pumpKeywords.isEmpty()) {
|
||||
copyInPlay.addChangedCardKeywords(pumpKeywords, Lists.<String>newArrayList(), false, false, timestamp);
|
||||
}
|
||||
@@ -266,7 +264,6 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
|
||||
final List<String> svars = Lists.newArrayList();
|
||||
final List<String> triggers = Lists.newArrayList();
|
||||
boolean asNonLegendary = false;
|
||||
boolean resetActivations = false;
|
||||
|
||||
if (sa.hasParam("Keywords")) {
|
||||
keywords.addAll(Arrays.asList(sa.getParam("Keywords").split(" & ")));
|
||||
@@ -277,9 +274,6 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("NonLegendary")) {
|
||||
asNonLegendary = true;
|
||||
}
|
||||
if (sa.hasParam("ResetAbilityActivations")) {
|
||||
resetActivations = true;
|
||||
}
|
||||
if (sa.hasParam("AddSVars")) {
|
||||
svars.addAll(Arrays.asList(sa.getParam("AddSVars").split(" & ")));
|
||||
}
|
||||
@@ -411,11 +405,6 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
|
||||
copy.removeIntrinsicKeyword("Devoid");
|
||||
}
|
||||
|
||||
if (resetActivations) {
|
||||
for (SpellAbility ab : copy.getSpellAbilities()) {
|
||||
ab.getRestrictions().resetTurnActivations();
|
||||
}
|
||||
}
|
||||
copy.updateStateForView();
|
||||
return copy;
|
||||
}
|
||||
|
||||
@@ -199,6 +199,14 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
counterAmount = pc.chooseNumber(sa, "How many counters?", 0, counterAmount, params);
|
||||
}
|
||||
|
||||
// Adapt need extra logic
|
||||
if (sa.hasParam("Adapt")) {
|
||||
if (!(tgtCard.getCounters(CounterType.P1P1) == 0
|
||||
|| tgtCard.hasKeyword("CARDNAME adapts as though it had no +1/+1 counters"))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("Tribute")) {
|
||||
// make a copy to check if it would be on the battlefield
|
||||
Card noTributeLKI = CardUtil.getLKICopy(tgtCard);
|
||||
@@ -266,6 +274,13 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
runParams.put("Card", tgtCard);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.BecomeRenowned, runParams, false);
|
||||
}
|
||||
if (sa.hasParam("Adapt")) {
|
||||
// need to remove special keyword
|
||||
tgtCard.removeHiddenExtrinsicKeyword("CARDNAME adapts as though it had no +1/+1 counters");
|
||||
final Map<String, Object> runParams = Maps.newHashMap();
|
||||
runParams.put("Card", tgtCard);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Adapt, runParams, false);
|
||||
}
|
||||
} else {
|
||||
// adding counters to something like re-suspend cards
|
||||
// etbcounter should apply multiplier
|
||||
|
||||
@@ -4,6 +4,8 @@ import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController;
|
||||
@@ -69,11 +71,14 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
|
||||
final Card card = sa.getHostCard();
|
||||
final Game game = card.getGame();
|
||||
final Player player = sa.getActivatingPlayer();
|
||||
|
||||
PlayerController pc = player.getController();
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String num = sa.getParam("CounterNum");
|
||||
|
||||
int cntToRemove = 0;
|
||||
if (!num.equals("All") && !num.equals("Remembered")) {
|
||||
if (!num.equals("All") && !num.equals("Any") && !num.equals("Remembered")) {
|
||||
cntToRemove = AbilityUtils.calculateAmount(sa.getHostCard(), num, sa);
|
||||
}
|
||||
|
||||
@@ -96,6 +101,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
boolean rememberRemoved = sa.hasParam("RememberRemoved");
|
||||
boolean rememberAmount = sa.hasParam("RememberAmount");
|
||||
|
||||
for (final Player tgtPlayer : getTargetPlayers(sa)) {
|
||||
// Removing energy
|
||||
@@ -107,7 +113,23 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
for (final Card tgtCard : getTargetCards(sa)) {
|
||||
CardCollectionView srcCards = null;
|
||||
if (sa.hasParam("ValidSource")) {
|
||||
srcCards = game.getCardsIn(ZoneType.Battlefield);
|
||||
srcCards = CardLists.getValidCards(srcCards, sa.getParam("ValidSource"), player, card, sa);
|
||||
if (num.equals("Any")) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Choose cards to take ").append(counterType.getName()).append(" counters from");
|
||||
|
||||
srcCards = player.getController().chooseCardsForEffect(srcCards, sa, sb.toString(), 0, srcCards.size(), true);
|
||||
}
|
||||
} else {
|
||||
srcCards = getTargetCards(sa);
|
||||
}
|
||||
|
||||
int totalRemoved = 0;
|
||||
|
||||
for (final Card tgtCard : srcCards) {
|
||||
Card gameCard = game.getCardState(tgtCard, null);
|
||||
// gameCard is LKI in that case, the card is not in game anymore
|
||||
// or the timestamp did change
|
||||
@@ -123,14 +145,12 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
game.updateLastStateForCard(gameCard);
|
||||
continue;
|
||||
} else if (num.equals("All")) {
|
||||
} else if (num.equals("All") || num.equals("Any")) {
|
||||
cntToRemove = gameCard.getCounters(counterType);
|
||||
} else if (sa.getParam("CounterNum").equals("Remembered")) {
|
||||
} else if (num.equals("Remembered")) {
|
||||
cntToRemove = gameCard.getCountersAddedBy(card, counterType);
|
||||
}
|
||||
|
||||
PlayerController pc = sa.getActivatingPlayer().getController();
|
||||
|
||||
|
||||
if (type.equals("Any")) {
|
||||
while (cntToRemove > 0 && gameCard.hasCounters()) {
|
||||
final Map<CounterType, Integer> tgtCounters = gameCard.getCounters();
|
||||
@@ -162,7 +182,7 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
cntToRemove = Math.min(cntToRemove, gameCard.getCounters(counterType));
|
||||
|
||||
if (zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Exile)) {
|
||||
if (sa.hasParam("UpTo")) {
|
||||
if (sa.hasParam("UpTo") || num.equals("Any")) {
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Target", gameCard);
|
||||
params.put("CounterType", type);
|
||||
@@ -179,10 +199,17 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
game.updateLastStateForCard(gameCard);
|
||||
|
||||
totalRemoved += cntToRemove;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (totalRemoved > 0 && rememberAmount) {
|
||||
// TODO use SpellAbility Remember later
|
||||
card.addRemembered(Integer.valueOf(totalRemoved));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -121,6 +121,9 @@ public class DamageAllEffect extends DamageBaseEffect {
|
||||
if (!usedDamageMap) {
|
||||
preventMap.triggerPreventDamage(false);
|
||||
damageMap.triggerDamageDoneOnce(false, sa);
|
||||
|
||||
preventMap.clear();
|
||||
damageMap.clear();
|
||||
}
|
||||
|
||||
replaceDying(sa);
|
||||
|
||||
@@ -30,7 +30,7 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
final int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
|
||||
|
||||
List<GameObject> tgts = getTargets(sa);
|
||||
if (tgts.isEmpty())
|
||||
if (tgts.isEmpty())
|
||||
return "";
|
||||
|
||||
final List<Card> definedSources = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DamageSource"), sa);
|
||||
@@ -131,15 +131,15 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
sa.setPreventMap(preventMap);
|
||||
usedDamageMap = true;
|
||||
}
|
||||
|
||||
|
||||
final List<Card> definedSources = AbilityUtils.getDefinedCards(hostCard, sa.getParam("DamageSource"), sa);
|
||||
if (definedSources == null || definedSources.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
for (Card source : definedSources) {
|
||||
final Card sourceLKI = hostCard.getGame().getChangeZoneLKIInfo(source);
|
||||
|
||||
|
||||
if (divideOnResolution) {
|
||||
// Dividing Damage up to multiple targets using combat damage box
|
||||
// Currently only used for Master of the Wild Hunt
|
||||
@@ -147,7 +147,7 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
if (players.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
CardCollection assigneeCards = new CardCollection();
|
||||
// Do we have a way of doing this in a better fashion?
|
||||
for (GameObject obj : tgts) {
|
||||
@@ -155,7 +155,7 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
assigneeCards.add((Card)obj);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Player assigningPlayer = players.get(0);
|
||||
Map<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(sourceLKI, assigneeCards, dmg, null, true);
|
||||
for (Entry<Card, Integer> dt : map.entrySet()) {
|
||||
@@ -166,6 +166,9 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
preventMap.triggerPreventDamage(false);
|
||||
// non combat damage cause lifegain there
|
||||
damageMap.triggerDamageDoneOnce(false, sa);
|
||||
|
||||
preventMap.clear();
|
||||
damageMap.clear();
|
||||
}
|
||||
replaceDying(sa);
|
||||
return;
|
||||
@@ -201,7 +204,7 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (remember) {
|
||||
source.addRemembered(damageMap.row(sourceLKI).keySet());
|
||||
}
|
||||
@@ -210,6 +213,9 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
preventMap.triggerPreventDamage(false);
|
||||
// non combat damage cause lifegain there
|
||||
damageMap.triggerDamageDoneOnce(false, sa);
|
||||
|
||||
preventMap.clear();
|
||||
damageMap.clear();
|
||||
}
|
||||
replaceDying(sa);
|
||||
}
|
||||
|
||||
@@ -132,6 +132,9 @@ public class DamageEachEffect extends DamageBaseEffect {
|
||||
if (!usedDamageMap) {
|
||||
preventMap.triggerPreventDamage(false);
|
||||
damageMap.triggerDamageDoneOnce(false, sa);
|
||||
|
||||
preventMap.clear();
|
||||
damageMap.clear();
|
||||
}
|
||||
|
||||
replaceDying(sa);
|
||||
|
||||
@@ -21,10 +21,12 @@ public class DamageResolveEffect extends SpellAbilityEffect {
|
||||
|
||||
if (preventMap != null) {
|
||||
preventMap.triggerPreventDamage(false);
|
||||
preventMap.clear();
|
||||
}
|
||||
// non combat damage cause lifegain there
|
||||
if (damageMap != null) {
|
||||
damageMap.triggerDamageDoneOnce(false, sa);
|
||||
damageMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -187,14 +187,14 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
if (!andOrValid.equals("")) {
|
||||
andOrCards = CardLists.getValidCards(top, andOrValid.split(","), host.getController(), host, sa);
|
||||
andOrCards.removeAll((Collection<?>)valid);
|
||||
valid.addAll(andOrCards);
|
||||
valid.addAll(andOrCards); //pfps need to add andOr cards to valid to have set of all valid cards set up
|
||||
}
|
||||
else {
|
||||
andOrCards = new CardCollection();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If all the cards are valid choices, no need for a separate reveal dialog to the chooser.
|
||||
// If all the cards are valid choices, no need for a separate reveal dialog to the chooser. pfps??
|
||||
if (p == chooser && destZone1ChangeNum > 1) {
|
||||
delayedReveal = null;
|
||||
}
|
||||
@@ -238,54 +238,42 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("RandomOrder")) {
|
||||
CardLists.shuffle(movedCards);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
String prompt;
|
||||
|
||||
|
||||
if (sa.hasParam("PrimaryPrompt")) {
|
||||
prompt = sa.getParam("PrimaryPrompt");
|
||||
} else {
|
||||
prompt = "Choose a card to put into " + destZone1.name();
|
||||
prompt = "Choose card(s) to put into " + destZone1.name();
|
||||
if (destZone1.equals(ZoneType.Library)) {
|
||||
if (libraryPosition == -1) {
|
||||
prompt = "Choose a card to put on the bottom of {player's} library";
|
||||
}
|
||||
else if (libraryPosition == 0) {
|
||||
prompt = "Choose a card to put on top of {player's} library";
|
||||
prompt = "Choose card(s) to put on the bottom of {player's} library";
|
||||
} else if (libraryPosition == 0) {
|
||||
prompt = "Choose card(s) to put on top of {player's} library";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
movedCards = new CardCollection();
|
||||
for (int i = 0; i < destZone1ChangeNum || (anyNumber && i < numToDig); i++) {
|
||||
// let user get choice
|
||||
Card chosen = null;
|
||||
if (!valid.isEmpty()) {
|
||||
// If we're choosing multiple cards, only need to show the reveal dialog the first time through.
|
||||
boolean shouldReveal = (i == 0);
|
||||
chosen = chooser.getController().chooseSingleEntityForEffect(valid, shouldReveal ? delayedReveal : null, sa, prompt, anyNumber || optional, p);
|
||||
if (valid.isEmpty()) {
|
||||
chooser.getController().notifyOfValue(sa, null, "No valid cards");
|
||||
} else {
|
||||
if ( p == chooser ) { // the digger can still see all the dug cards when choosing
|
||||
chooser.getController().tempShowCards(top);
|
||||
}
|
||||
else {
|
||||
if (i == 0) {
|
||||
chooser.getController().notifyOfValue(sa, null, "No valid cards");
|
||||
}
|
||||
}
|
||||
|
||||
if (chosen == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
movedCards.add(chosen);
|
||||
valid.remove(chosen);
|
||||
List<Card> chosen = new ArrayList<Card>();
|
||||
if (!andOrValid.equals("")) {
|
||||
andOrCards.remove(chosen);
|
||||
if (!chosen.isValid(andOrValid.split(","), host.getController(), host, sa)) {
|
||||
valid = new CardCollection(andOrCards);
|
||||
}
|
||||
else if (!chosen.isValid(changeValid.split(","), host.getController(), host, sa)) {
|
||||
valid.removeAll((Collection<?>)andOrCards);
|
||||
valid.removeAll(andOrCards); //pfps remove andOr cards to get two two choices set up correctly
|
||||
chosen = chooser.getController().chooseFromTwoListsForEffect(valid, andOrCards, optional, delayedReveal, sa, prompt, p);
|
||||
} else {
|
||||
int max = anyNumber ? valid.size() : Math.min(valid.size(),destZone1ChangeNum);
|
||||
int min = (anyNumber || optional) ? 0 : max;
|
||||
if ( max > 0 ) { // if max is 0 don't make a choice
|
||||
chosen = chooser.getController().chooseEntitiesForEffect(valid, min, max, delayedReveal, sa, prompt, p);
|
||||
}
|
||||
}
|
||||
chooser.getController().endTempShowCards();
|
||||
movedCards.addAll(chosen);
|
||||
}
|
||||
|
||||
if (!changeValid.isEmpty() && !sa.hasParam("ExileFaceDown") && !sa.hasParam("NoReveal")) {
|
||||
@@ -350,8 +338,7 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
CardCollection afterOrder = rest;
|
||||
if (sa.hasParam("RestRandomOrder")) {
|
||||
CardLists.shuffle(afterOrder);
|
||||
}
|
||||
else if (!skipReorder && rest.size() > 1) {
|
||||
} else if (!skipReorder && rest.size() > 1) {
|
||||
if (destZone2 == ZoneType.Graveyard) {
|
||||
afterOrder = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, rest, destZone2);
|
||||
} else {
|
||||
|
||||
@@ -153,6 +153,9 @@ public class FightEffect extends DamageBaseEffect {
|
||||
if (!usedDamageMap) {
|
||||
preventMap.triggerPreventDamage(false);
|
||||
damageMap.triggerDamageDoneOnce(false, sa);
|
||||
|
||||
preventMap.clear();
|
||||
damageMap.clear();
|
||||
}
|
||||
|
||||
replaceDying(sa);
|
||||
|
||||
@@ -54,9 +54,9 @@ public class MeldEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
primary.changeToState(CardStateName.Meld);
|
||||
primary.setMeldedWith(secondary);
|
||||
PlayerZoneBattlefield bf = (PlayerZoneBattlefield)controller.getZone(ZoneType.Battlefield);
|
||||
Card melded = game.getAction().changeZone(primary.getZone(), bf, primary, 0, sa);
|
||||
game.getAction().changeZone(primary.getZone(), bf, primary, 0, sa);
|
||||
bf.addToMelded(secondary);
|
||||
melded.setMeldedWith(secondary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
|
||||
final Player controller = activator;
|
||||
CardCollection tgtCards;
|
||||
CardCollection showCards = new CardCollection();
|
||||
|
||||
if (sa.hasParam("Valid")) {
|
||||
ZoneType zone = ZoneType.Hand;
|
||||
@@ -81,6 +82,9 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
tgtCards = new CardCollection(
|
||||
AbilityUtils.filterListByType(game.getCardsIn(zone), sa.getParam("Valid"), sa)
|
||||
);
|
||||
if ( sa.hasParam("ShowCards") ) {
|
||||
showCards = new CardCollection(AbilityUtils.filterListByType(game.getCardsIn(zone), sa.getParam("ShowCards"), sa));
|
||||
}
|
||||
}
|
||||
else if (sa.hasParam("AnySupportedCard")) {
|
||||
List<PaperCard> cards = Lists.newArrayList(StaticData.instance().getCommonCards().getUniqueCards());
|
||||
@@ -140,7 +144,9 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
|
||||
final CardCollection saidNoTo = new CardCollection();
|
||||
while (tgtCards.size() > saidNoTo.size() && saidNoTo.size() < amount && amount > 0) {
|
||||
activator.getController().tempShowCards(showCards);
|
||||
Card tgtCard = controller.getController().chooseSingleEntityForEffect(tgtCards, sa, "Select a card to play");
|
||||
activator.getController().endTempShowCards();
|
||||
if (tgtCard == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -94,32 +94,13 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
game.fireEvent(new GameEventCardStatsChanged(gameCard));
|
||||
}
|
||||
};
|
||||
if (sa.hasParam("UntilEndOfCombat")) {
|
||||
game.getEndOfCombat().addUntil(untilEOT);
|
||||
} else if (sa.hasParam("UntilYourNextUpkeep")) {
|
||||
game.getUpkeep().addUntil(sa.getActivatingPlayer(), untilEOT);
|
||||
} else if (sa.hasParam("UntilHostLeavesPlay")) {
|
||||
sa.getHostCard().addLeavesPlayCommand(untilEOT);
|
||||
} else if (sa.hasParam("UntilHostLeavesPlayOrEOT")) {
|
||||
sa.getHostCard().addLeavesPlayCommand(untilEOT);
|
||||
game.getEndOfTurn().addUntil(untilEOT);
|
||||
} else if (sa.hasParam("UntilLoseControlOfHost")) {
|
||||
sa.getHostCard().addLeavesPlayCommand(untilEOT);
|
||||
sa.getHostCard().addChangeControllerCommand(untilEOT);
|
||||
} else if (sa.hasParam("UntilYourNextTurn")) {
|
||||
game.getCleanup().addUntil(sa.getActivatingPlayer(), untilEOT);
|
||||
} else if (sa.hasParam("UntilUntaps")) {
|
||||
sa.getHostCard().addUntapCommand(untilEOT);
|
||||
} else {
|
||||
game.getEndOfTurn().addUntil(untilEOT);
|
||||
}
|
||||
addUntilCommand(sa, untilEOT);
|
||||
}
|
||||
game.fireEvent(new GameEventCardStatsChanged(gameCard));
|
||||
}
|
||||
|
||||
private static void applyPump(final SpellAbility sa, final Player p,
|
||||
final List<String> keywords, final long timestamp) {
|
||||
final Game game = p.getGame();
|
||||
final Card host = sa.getHostCard();
|
||||
//if host is not on the battlefield don't apply
|
||||
// Suspend should does Affect the Stack
|
||||
@@ -144,16 +125,32 @@ public class PumpEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
};
|
||||
if (sa.hasParam("UntilEndOfCombat")) {
|
||||
game.getEndOfCombat().addUntil(untilEOT);
|
||||
} else if (sa.hasParam("UntilYourNextUpkeep")) {
|
||||
game.getUpkeep().addUntil(sa.getActivatingPlayer(), untilEOT);
|
||||
} else if (sa.hasParam("UntilLoseControlOfHost")) {
|
||||
sa.getHostCard().addLeavesPlayCommand(untilEOT);
|
||||
sa.getHostCard().addChangeControllerCommand(untilEOT);
|
||||
} else {
|
||||
game.getEndOfTurn().addUntil(untilEOT);
|
||||
}
|
||||
addUntilCommand(sa, untilEOT);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addUntilCommand(final SpellAbility sa, GameCommand untilEOT) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
|
||||
if (sa.hasParam("UntilEndOfCombat")) {
|
||||
game.getEndOfCombat().addUntil(untilEOT);
|
||||
} else if (sa.hasParam("UntilYourNextUpkeep")) {
|
||||
game.getUpkeep().addUntil(sa.getActivatingPlayer(), untilEOT);
|
||||
} else if (sa.hasParam("UntilHostLeavesPlay")) {
|
||||
host.addLeavesPlayCommand(untilEOT);
|
||||
} else if (sa.hasParam("UntilHostLeavesPlayOrEOT")) {
|
||||
host.addLeavesPlayCommand(untilEOT);
|
||||
game.getEndOfTurn().addUntil(untilEOT);
|
||||
} else if (sa.hasParam("UntilLoseControlOfHost")) {
|
||||
host.addLeavesPlayCommand(untilEOT);
|
||||
host.addChangeControllerCommand(untilEOT);
|
||||
} else if (sa.hasParam("UntilYourNextTurn")) {
|
||||
game.getCleanup().addUntil(sa.getActivatingPlayer(), untilEOT);
|
||||
} else if (sa.hasParam("UntilUntaps")) {
|
||||
host.addUntapCommand(untilEOT);
|
||||
} else {
|
||||
game.getEndOfTurn().addUntil(untilEOT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,17 +4,17 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
|
||||
public class ScryEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
final List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
|
||||
for (final Player p : tgtPlayers) {
|
||||
for (final Player p : getTargetPlayers(sa)) {
|
||||
sb.append(p.toString()).append(" ");
|
||||
}
|
||||
|
||||
@@ -36,19 +36,16 @@ public class ScryEffect extends SpellAbilityEffect {
|
||||
|
||||
boolean isOptional = sa.hasParam("Optional");
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
final List<Player> players = Lists.newArrayList(); // players really affected
|
||||
|
||||
for (final Player p : tgtPlayers) {
|
||||
if ((tgt == null) || p.canBeTargetedBy(sa)) {
|
||||
if (isOptional && !p.getController().confirmAction(sa, null, "Do you want to scry?")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
p.scry(num, sa);
|
||||
}
|
||||
}
|
||||
// Optional here for spells that have optional multi-player scrying
|
||||
for (final Player p : getTargetPlayers(sa)) {
|
||||
if ( (!sa.usesTargeting() || p.canBeTargetedBy(sa)) &&
|
||||
(!isOptional || p.getController().confirmAction(sa, null, "Do you want to scry?")) ) {
|
||||
players.add(p);
|
||||
}
|
||||
}
|
||||
sa.getActivatingPlayer().getGame().getAction().scry(players, num, sa);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -493,21 +493,27 @@ public class TokenEffect extends SpellAbilityEffect {
|
||||
|
||||
lki.setLastKnownZone(tok.getController().getZone(ZoneType.Battlefield));
|
||||
|
||||
// double freeze tracker, so it doesn't update view
|
||||
game.getTracker().freeze();
|
||||
CardCollection preList = new CardCollection(lki);
|
||||
game.getAction().checkStaticAbilities(false, Sets.newHashSet(lki), preList);
|
||||
|
||||
// TODO update when doing Attach Update
|
||||
boolean canAttach = lki.isAttachment();
|
||||
|
||||
if (canAttach && ge.canBeAttached(lki)) {
|
||||
if (canAttach && !ge.canBeAttached(lki)) {
|
||||
canAttach = false;
|
||||
}
|
||||
|
||||
// reset static abilities
|
||||
game.getAction().checkStaticAbilities(false);
|
||||
// clear delayed changes, this check should not have updated the view
|
||||
game.getTracker().clearDelayed();
|
||||
// need to unfreeze tracker
|
||||
game.getTracker().unfreeze();
|
||||
|
||||
if (!canAttach) {
|
||||
// Token can't attach it
|
||||
// Token can't attach to it
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
|
||||
// cards attached or otherwise linked to this card
|
||||
private CardCollection hauntedBy, devouredCards, delvedCards, convokedCards, imprintedCards, encodedCards;
|
||||
private CardCollection mustBlockCards, clones, gainControlTargets, chosenCards, blockedThisTurn, blockedByThisTurn;
|
||||
private CardCollection mustBlockCards, gainControlTargets, chosenCards, blockedThisTurn, blockedByThisTurn;
|
||||
|
||||
// if this card is attached or linked to something, what card is it currently attached to
|
||||
private Card encoding, cloneOrigin, haunting, effectSource, pairedWith, meldedWith;
|
||||
@@ -528,30 +528,30 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
|
||||
public Card manifest(Player p, SpellAbility sa) {
|
||||
// Turn Face Down (even if it's DFC).
|
||||
CardState originalCard = this.getState(CardStateName.Original);
|
||||
ManaCost cost = originalCard.getManaCost();
|
||||
ManaCost cost = getState(CardStateName.Original).getManaCost();
|
||||
|
||||
boolean isCreature = this.isCreature();
|
||||
boolean isCreature = isCreature();
|
||||
|
||||
// Sometimes cards are manifested while already being face down
|
||||
if (!turnFaceDown(true) && currentStateName != CardStateName.FaceDown) {
|
||||
return null;
|
||||
}
|
||||
// Sometimes cards are manifested while already being face down
|
||||
if (!turnFaceDown(true) && !isFaceDown()) {
|
||||
return null;
|
||||
}
|
||||
// Move to p's battlefield
|
||||
Game game = p.getGame();
|
||||
// Just in case you aren't the controller, now you are!
|
||||
this.setController(p, game.getNextTimestamp());
|
||||
|
||||
// Just in case you aren't the controller, now you are!
|
||||
setController(p, game.getNextTimestamp());
|
||||
|
||||
// Mark this card as "manifested"
|
||||
this.setPreFaceDownState(CardStateName.Original);
|
||||
this.setManifested(true);
|
||||
setPreFaceDownState(CardStateName.Original);
|
||||
setManifested(true);
|
||||
|
||||
Card c = game.getAction().moveToPlay(this, p, sa);
|
||||
|
||||
// Add manifest demorph static ability for creatures
|
||||
if (isCreature && !cost.isNoCost()) {
|
||||
c.addSpellAbility(CardFactoryUtil.abilityManifestFaceUp(c, cost));
|
||||
|
||||
// Add Manifest to original State
|
||||
c.getState(CardStateName.Original).addSpellAbility(CardFactoryUtil.abilityManifestFaceUp(c, cost));
|
||||
c.updateStateForView();
|
||||
}
|
||||
|
||||
@@ -1031,22 +1031,6 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
public final GameEntity getMustAttackEntityThisTurn() { return mustAttackEntityThisTurn; }
|
||||
public final void setMustAttackEntityThisTurn(GameEntity entThisTurn) { mustAttackEntityThisTurn = entThisTurn; }
|
||||
|
||||
public final CardCollectionView getClones() {
|
||||
return CardCollection.getView(clones);
|
||||
}
|
||||
public final void setClones(final Iterable<Card> clones0) {
|
||||
clones = clones0 == null ? null : new CardCollection(clones0);
|
||||
}
|
||||
public final void addClone(final Card c) {
|
||||
if (clones == null) {
|
||||
clones = new CardCollection();
|
||||
}
|
||||
clones.add(c);
|
||||
}
|
||||
public final void clearClones() {
|
||||
clones = null;
|
||||
}
|
||||
|
||||
public final Card getCloneOrigin() {
|
||||
return cloneOrigin;
|
||||
}
|
||||
@@ -1067,7 +1051,8 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
}
|
||||
|
||||
public final boolean hasConverge() {
|
||||
return "Count$Converge".equals(getSVar("X")) || "Count$Converge".equals(getSVar("Y")) || hasKeyword("Sunburst");
|
||||
return "Count$Converge".equals(getSVar("X")) || "Count$Converge".equals(getSVar("Y")) ||
|
||||
hasKeyword(Keyword.SUNBURST) || hasKeyword("Modular:Sunburst");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1463,250 +1448,262 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
int i = 0;
|
||||
for (KeywordInterface inst : keywords) {
|
||||
String keyword = inst.getOriginal();
|
||||
if (keyword.startsWith("SpellCantTarget")) {
|
||||
continue;
|
||||
}
|
||||
// format text changes
|
||||
if (CardUtil.isKeywordModifiable(keyword)
|
||||
&& keywordsGrantedByTextChanges.contains(inst)) {
|
||||
for (final Entry<String, String> e : textChanges) {
|
||||
final String value = e.getValue();
|
||||
if (keyword.contains(value)) {
|
||||
keyword = TextUtil.fastReplace(keyword, value,
|
||||
TextUtil.concatNoSpace("<strike>", e.getKey(), "</strike> ", value));
|
||||
// assume (for now) max one change per keyword
|
||||
break;
|
||||
try {
|
||||
if (keyword.startsWith("SpellCantTarget")) {
|
||||
continue;
|
||||
}
|
||||
// format text changes
|
||||
if (CardUtil.isKeywordModifiable(keyword)
|
||||
&& keywordsGrantedByTextChanges.contains(inst)) {
|
||||
for (final Entry<String, String> e : textChanges) {
|
||||
final String value = e.getValue();
|
||||
if (keyword.contains(value)) {
|
||||
keyword = TextUtil.fastReplace(keyword, value,
|
||||
TextUtil.concatNoSpace("<strike>", e.getKey(), "</strike> ", value));
|
||||
// assume (for now) max one change per keyword
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (keyword.startsWith("CantBeCounteredBy")) {
|
||||
final String[] p = keyword.split(":");
|
||||
sbLong.append(p[2]).append("\r\n");
|
||||
} else if (keyword.startsWith("etbCounter")) {
|
||||
final String[] p = keyword.split(":");
|
||||
final StringBuilder s = new StringBuilder();
|
||||
if (p.length > 4) {
|
||||
if (!"no desc".equals(p[4])) {
|
||||
s.append(p[4]);
|
||||
if (keyword.startsWith("CantBeCounteredBy") || keyword.startsWith("Panharmonicon")
|
||||
|| keyword.startsWith("Dieharmonicon")) {
|
||||
final String[] p = keyword.split(":");
|
||||
sbLong.append(p[2]).append("\r\n");
|
||||
} else if (keyword.startsWith("etbCounter")) {
|
||||
final String[] p = keyword.split(":");
|
||||
final StringBuilder s = new StringBuilder();
|
||||
if (p.length > 4) {
|
||||
if (!"no desc".equals(p[4])) {
|
||||
s.append(p[4]);
|
||||
}
|
||||
} else {
|
||||
s.append(getName());
|
||||
s.append(" enters the battlefield with ");
|
||||
s.append(Lang.nounWithNumeral(p[2], CounterType.valueOf(p[1]).getName() + " counter"));
|
||||
s.append(" on it.");
|
||||
}
|
||||
} else {
|
||||
s.append(getName());
|
||||
s.append(" enters the battlefield with ");
|
||||
s.append(Lang.nounWithNumeral(p[2], CounterType.valueOf(p[1]).getName() + " counter"));
|
||||
s.append(" on it.");
|
||||
}
|
||||
sbLong.append(s).append("\r\n");
|
||||
} else if (keyword.startsWith("Protection:")) {
|
||||
final String[] k = keyword.split(":");
|
||||
sbLong.append(k[2]).append("\r\n");
|
||||
} else if (keyword.startsWith("Creatures can't attack unless their controller pays")) {
|
||||
final String[] k = keyword.split(":");
|
||||
if (!k[3].equals("no text")) {
|
||||
sbLong.append(k[3]).append("\r\n");
|
||||
}
|
||||
} else if (keyword.startsWith("Enchant")) {
|
||||
String k = keyword;
|
||||
k = TextUtil.fastReplace(k, "Curse", "");
|
||||
sbLong.append(k).append("\r\n");
|
||||
} else if (keyword.startsWith("Ripple")) {
|
||||
sbLong.append(TextUtil.fastReplace(keyword, ":", " ")).append("\r\n");
|
||||
} else if (keyword.startsWith("Madness")) {
|
||||
String[] parts = keyword.split(":");
|
||||
// If no colon exists in Madness keyword, it must have been granted and assumed the cost from host
|
||||
if (parts.length < 2) {
|
||||
sbLong.append(parts[0]).append(" ").append(this.getManaCost()).append("\r\n");
|
||||
} else {
|
||||
sbLong.append(parts[0]).append(" ").append(ManaCostParser.parse(parts[1])).append("\r\n");
|
||||
}
|
||||
} else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph")) {
|
||||
String[] k = keyword.split(":");
|
||||
sbLong.append(k[0]);
|
||||
if (k.length > 1) {
|
||||
final Cost mCost = new Cost(k[1], true);
|
||||
if (!mCost.isOnlyManaCost()) {
|
||||
sbLong.append("—");
|
||||
sbLong.append(s).append("\r\n");
|
||||
} else if (keyword.startsWith("Protection:")) {
|
||||
final String[] k = keyword.split(":");
|
||||
sbLong.append(k[2]).append("\r\n");
|
||||
} else if (keyword.startsWith("Creatures can't attack unless their controller pays")) {
|
||||
final String[] k = keyword.split(":");
|
||||
if (!k[3].equals("no text")) {
|
||||
sbLong.append(k[3]).append("\r\n");
|
||||
}
|
||||
if (mCost.isOnlyManaCost()) {
|
||||
sbLong.append(" ");
|
||||
} else if (keyword.startsWith("Enchant")) {
|
||||
String k = keyword;
|
||||
k = TextUtil.fastReplace(k, "Curse", "");
|
||||
sbLong.append(k).append("\r\n");
|
||||
} else if (keyword.startsWith("Ripple")) {
|
||||
sbLong.append(TextUtil.fastReplace(keyword, ":", " ")).append("\r\n");
|
||||
} else if (keyword.startsWith("Madness")) {
|
||||
String[] parts = keyword.split(":");
|
||||
// If no colon exists in Madness keyword, it must have been granted and assumed the cost from host
|
||||
if (parts.length < 2) {
|
||||
sbLong.append(parts[0]).append(" ").append(this.getManaCost()).append("\r\n");
|
||||
} else {
|
||||
sbLong.append(parts[0]).append(" ").append(ManaCostParser.parse(parts[1])).append("\r\n");
|
||||
}
|
||||
sbLong.append(mCost.toString()).delete(sbLong.length() - 2, sbLong.length());
|
||||
if (!mCost.isOnlyManaCost()) {
|
||||
sbLong.append(".");
|
||||
} else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph")) {
|
||||
String[] k = keyword.split(":");
|
||||
sbLong.append(k[0]);
|
||||
if (k.length > 1) {
|
||||
final Cost mCost = new Cost(k[1], true);
|
||||
if (!mCost.isOnlyManaCost()) {
|
||||
sbLong.append("—");
|
||||
}
|
||||
if (mCost.isOnlyManaCost()) {
|
||||
sbLong.append(" ");
|
||||
}
|
||||
sbLong.append(mCost.toString()).delete(sbLong.length() - 2, sbLong.length());
|
||||
if (!mCost.isOnlyManaCost()) {
|
||||
sbLong.append(".");
|
||||
}
|
||||
sbLong.append(" (" + inst.getReminderText() + ")");
|
||||
sbLong.append("\r\n");
|
||||
}
|
||||
} else if (keyword.startsWith("Emerge")) {
|
||||
final String[] k = keyword.split(":");
|
||||
sbLong.append(k[0]).append(" ").append(ManaCostParser.parse(k[1]));
|
||||
sbLong.append(" (" + inst.getReminderText() + ")");
|
||||
sbLong.append("\r\n");
|
||||
}
|
||||
} else if (keyword.startsWith("Emerge")) {
|
||||
final String[] k = keyword.split(":");
|
||||
sbLong.append(k[0]).append(" ").append(ManaCostParser.parse(k[1]));
|
||||
sbLong.append(" (" + inst.getReminderText() + ")");
|
||||
sbLong.append("\r\n");
|
||||
} else if (keyword.startsWith("Echo")) {
|
||||
sbLong.append("Echo ");
|
||||
final String[] upkeepCostParams = keyword.split(":");
|
||||
sbLong.append(upkeepCostParams.length > 2 ? "- " + upkeepCostParams[2] : ManaCostParser.parse(upkeepCostParams[1]));
|
||||
sbLong.append(" (At the beginning of your upkeep, if CARDNAME came under your control since the beginning of your last upkeep, sacrifice it unless you pay its echo cost.)");
|
||||
sbLong.append("\r\n");
|
||||
} else if (keyword.startsWith("Cumulative upkeep")) {
|
||||
sbLong.append("Cumulative upkeep ");
|
||||
final String[] upkeepCostParams = keyword.split(":");
|
||||
sbLong.append(upkeepCostParams.length > 2 ? "- " + upkeepCostParams[2] : ManaCostParser.parse(upkeepCostParams[1]));
|
||||
sbLong.append("\r\n");
|
||||
} else if (keyword.startsWith("Alternative Cost")) {
|
||||
sbLong.append("Has alternative cost.");
|
||||
} else if (keyword.startsWith("AlternateAdditionalCost")) {
|
||||
final String costString1 = keyword.split(":")[1];
|
||||
final String costString2 = keyword.split(":")[2];
|
||||
final Cost cost1 = new Cost(costString1, false);
|
||||
final Cost cost2 = new Cost(costString2, false);
|
||||
sbLong.append("As an additional cost to cast ")
|
||||
.append(getName()).append(", ")
|
||||
.append(cost1.toSimpleString())
|
||||
.append(" or pay ")
|
||||
.append(cost2.toSimpleString())
|
||||
.append(".\r\n");
|
||||
} else if (keyword.startsWith("Multikicker")) {
|
||||
if (!keyword.endsWith("Generic")) {
|
||||
final String[] n = keyword.split(":");
|
||||
final Cost cost = new Cost(n[1], false);
|
||||
sbLong.append("Multikicker ").append(cost.toSimpleString());
|
||||
sbLong.append(" (" + inst.getReminderText() + ")").append("\r\n");
|
||||
}
|
||||
} else if (keyword.startsWith("Kicker")) {
|
||||
if (!keyword.endsWith("Generic")) {
|
||||
final StringBuilder sbx = new StringBuilder();
|
||||
final String[] n = keyword.split(":");
|
||||
sbx.append("Kicker ");
|
||||
final Cost cost = new Cost(n[1], false);
|
||||
sbx.append(cost.toSimpleString());
|
||||
if (Lists.newArrayList(n).size() > 2) {
|
||||
sbx.append(" and/or ");
|
||||
final Cost cost2 = new Cost(n[2], false);
|
||||
sbx.append(cost2.toSimpleString());
|
||||
} else if (keyword.startsWith("Echo")) {
|
||||
sbLong.append("Echo ");
|
||||
final String[] upkeepCostParams = keyword.split(":");
|
||||
sbLong.append(upkeepCostParams.length > 2 ? "- " + upkeepCostParams[2] : ManaCostParser.parse(upkeepCostParams[1]));
|
||||
sbLong.append(" (At the beginning of your upkeep, if CARDNAME came under your control since the beginning of your last upkeep, sacrifice it unless you pay its echo cost.)");
|
||||
sbLong.append("\r\n");
|
||||
} else if (keyword.startsWith("Cumulative upkeep")) {
|
||||
sbLong.append("Cumulative upkeep ");
|
||||
final String[] upkeepCostParams = keyword.split(":");
|
||||
sbLong.append(upkeepCostParams.length > 2 ? "- " + upkeepCostParams[2] : ManaCostParser.parse(upkeepCostParams[1]));
|
||||
sbLong.append("\r\n");
|
||||
} else if (keyword.startsWith("Alternative Cost")) {
|
||||
sbLong.append("Has alternative cost.");
|
||||
} else if (keyword.startsWith("AlternateAdditionalCost")) {
|
||||
final String costString1 = keyword.split(":")[1];
|
||||
final String costString2 = keyword.split(":")[2];
|
||||
final Cost cost1 = new Cost(costString1, false);
|
||||
final Cost cost2 = new Cost(costString2, false);
|
||||
sbLong.append("As an additional cost to cast ")
|
||||
.append(getName()).append(", ")
|
||||
.append(cost1.toSimpleString())
|
||||
.append(" or pay ")
|
||||
.append(cost2.toSimpleString())
|
||||
.append(".\r\n");
|
||||
} else if (keyword.startsWith("Multikicker")) {
|
||||
if (!keyword.endsWith("Generic")) {
|
||||
final String[] n = keyword.split(":");
|
||||
final Cost cost = new Cost(n[1], false);
|
||||
sbLong.append("Multikicker ").append(cost.toSimpleString());
|
||||
sbLong.append(" (" + inst.getReminderText() + ")").append("\r\n");
|
||||
}
|
||||
sbx.append(" (" + inst.getReminderText() + ")");
|
||||
sbLong.append(sbx).append("\r\n");
|
||||
}
|
||||
} else if (keyword.startsWith("Hexproof:")) {
|
||||
final String k[] = keyword.split(":");
|
||||
sbLong.append("Hexproof from ").append(k[2])
|
||||
.append(" (").append(inst.getReminderText()).append(")").append("\r\n");
|
||||
} else if (keyword.endsWith(".") && !keyword.startsWith("Haunt")) {
|
||||
sbLong.append(keyword).append("\r\n");
|
||||
} else if (keyword.startsWith("Presence") || keyword.startsWith("MayFlash")) {
|
||||
// Pseudo keywords, only print Reminder
|
||||
sbLong.append(inst.getReminderText());
|
||||
} else if (keyword.contains("At the beginning of your upkeep, ")
|
||||
&& keyword.contains(" unless you pay")) {
|
||||
sbLong.append(keyword).append("\r\n");
|
||||
} else if (keyword.startsWith("Strive") || keyword.startsWith("Escalate")
|
||||
|| keyword.startsWith("ETBReplacement")
|
||||
|| keyword.startsWith("CantBeBlockedBy ")
|
||||
|| keyword.startsWith("Affinity")
|
||||
|| keyword.equals("CARDNAME enters the battlefield tapped.")
|
||||
|| keyword.startsWith("UpkeepCost")) {
|
||||
} else if (keyword.equals("Provoke") || keyword.equals("Ingest") || keyword.equals("Unleash")
|
||||
|| keyword.equals("Soulbond") || keyword.equals("Partner") || keyword.equals("Retrace")
|
||||
|| keyword.equals("Living Weapon") || keyword.equals("Myriad") || keyword.equals("Exploit")
|
||||
|| keyword.equals("Changeling") || keyword.equals("Delve")
|
||||
|| keyword.equals("Split second")
|
||||
|| keyword.equals("Suspend") // for the ones without amounnt
|
||||
|| keyword.equals("Hideaway") || keyword.equals("Ascend")
|
||||
|| keyword.equals("Totem armor") || keyword.equals("Battle cry")
|
||||
|| keyword.equals("Devoid") || keyword.equals("Riot")){
|
||||
sbLong.append(keyword + " (" + inst.getReminderText() + ")");
|
||||
} else if (keyword.startsWith("Partner:")) {
|
||||
final String[] k = keyword.split(":");
|
||||
sbLong.append("Partner with " + k[1] + " (" + inst.getReminderText() + ")");
|
||||
} else if (keyword.startsWith("Modular") || keyword.startsWith("Bloodthirst") || keyword.startsWith("Dredge")
|
||||
|| keyword.startsWith("Fabricate") || keyword.startsWith("Soulshift") || keyword.startsWith("Bushido")
|
||||
|| keyword.startsWith("Crew") || keyword.startsWith("Tribute") || keyword.startsWith("Absorb")
|
||||
|| keyword.startsWith("Graft") || keyword.startsWith("Fading") || keyword.startsWith("Vanishing")
|
||||
|| keyword.startsWith("Afterlife")
|
||||
|| keyword.startsWith("Afflict") || keyword.startsWith ("Poisonous") || keyword.startsWith("Rampage")
|
||||
|| keyword.startsWith("Renown") || keyword.startsWith("Annihilator") || keyword.startsWith("Devour")) {
|
||||
final String[] k = keyword.split(":");
|
||||
sbLong.append(k[0] + " " + k[1] + " (" + inst.getReminderText() + ")");
|
||||
} else if (keyword.contains("Haunt")) {
|
||||
sb.append("\r\nHaunt (");
|
||||
if (isCreature()) {
|
||||
sb.append("When this creature dies, exile it haunting target creature.");
|
||||
} else {
|
||||
sb.append("When this spell card is put into a graveyard after resolving, ");
|
||||
sb.append("exile it haunting target creature.");
|
||||
}
|
||||
sb.append(")");
|
||||
} else if (keyword.equals("Convoke") || keyword.equals("Dethrone")|| keyword.equals("Fear")
|
||||
|| keyword.equals("Melee") || keyword.equals("Improvise")|| keyword.equals("Shroud")
|
||||
|| keyword.equals("Banding") || keyword.equals("Intimidate")|| keyword.equals("Evolve")
|
||||
|| keyword.equals("Exalted") || keyword.equals("Extort")|| keyword.equals("Flanking")
|
||||
|| keyword.equals("Horsemanship") || keyword.equals("Infect")|| keyword.equals("Persist")
|
||||
|| keyword.equals("Phasing") || keyword.equals("Shadow")|| keyword.equals("Skulk")
|
||||
|| keyword.equals("Undying") || keyword.equals("Wither") || keyword.equals("Cascade")
|
||||
|| keyword.equals("Mentor")) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append("\r\n");
|
||||
}
|
||||
sb.append(keyword + " (" + inst.getReminderText() + ")");
|
||||
} else if (keyword.endsWith(" offering")) {
|
||||
String offeringType = keyword.split(" ")[0];
|
||||
if (sb.length() != 0) {
|
||||
sb.append("\r\n");
|
||||
}
|
||||
sbLong.append(keyword);
|
||||
sbLong.append(" (" + Keyword.getInstance("Offering:"+ offeringType).getReminderText() + ")");
|
||||
} else if (keyword.startsWith("Equip") || keyword.startsWith("Fortify") || keyword.startsWith("Outlast")
|
||||
|| keyword.startsWith("Unearth") || keyword.startsWith("Scavenge") || keyword.startsWith("Spectacle")
|
||||
|| keyword.startsWith("Evoke") || keyword.startsWith("Bestow") || keyword.startsWith("Dash")
|
||||
|| keyword.startsWith("Surge") || keyword.startsWith("Transmute") || keyword.startsWith("Suspend")
|
||||
|| keyword.equals("Undaunted") || keyword.startsWith("Monstrosity") || keyword.startsWith("Embalm")
|
||||
|| keyword.startsWith("Level up") || keyword.equals("Prowess") || keyword.startsWith("Eternalize")
|
||||
|| keyword.startsWith("Reinforce") || keyword.startsWith("Champion") || keyword.startsWith("Prowl")
|
||||
|| keyword.startsWith("Amplify") || keyword.startsWith("Ninjutsu") || keyword.startsWith("Adapt")
|
||||
|| keyword.startsWith("Cycling") || keyword.startsWith("TypeCycling")) {
|
||||
// keyword parsing takes care of adding a proper description
|
||||
} else if (keyword.startsWith("CantBeBlockedByAmount")) {
|
||||
sbLong.append(getName()).append(" can't be blocked ");
|
||||
sbLong.append(getTextForKwCantBeBlockedByAmount(keyword));
|
||||
} else if (keyword.startsWith("CantBlock")) {
|
||||
sbLong.append(getName()).append(" can't block ");
|
||||
if (keyword.contains("CardUID")) {
|
||||
sbLong.append("CardID (").append(Integer.valueOf(keyword.split("CantBlockCardUID_")[1])).append(")");
|
||||
} else {
|
||||
} else if (keyword.startsWith("Kicker")) {
|
||||
if (!keyword.endsWith("Generic")) {
|
||||
final StringBuilder sbx = new StringBuilder();
|
||||
final String[] n = keyword.split(":");
|
||||
sbx.append("Kicker ");
|
||||
final Cost cost = new Cost(n[1], false);
|
||||
sbx.append(cost.toSimpleString());
|
||||
if (Lists.newArrayList(n).size() > 2) {
|
||||
sbx.append(" and/or ");
|
||||
final Cost cost2 = new Cost(n[2], false);
|
||||
sbx.append(cost2.toSimpleString());
|
||||
}
|
||||
sbx.append(" (" + inst.getReminderText() + ")");
|
||||
sbLong.append(sbx).append("\r\n");
|
||||
}
|
||||
} else if (keyword.startsWith("Hexproof:")) {
|
||||
final String k[] = keyword.split(":");
|
||||
sbLong.append("Hexproof from ").append(k[2])
|
||||
.append(" (").append(inst.getReminderText()).append(")").append("\r\n");
|
||||
} else if (keyword.endsWith(".") && !keyword.startsWith("Haunt")) {
|
||||
sbLong.append(keyword).append("\r\n");
|
||||
} else if (keyword.startsWith("Presence") || keyword.startsWith("MayFlash")) {
|
||||
// Pseudo keywords, only print Reminder
|
||||
sbLong.append(inst.getReminderText());
|
||||
} else if (keyword.contains("At the beginning of your upkeep, ")
|
||||
&& keyword.contains(" unless you pay")) {
|
||||
sbLong.append(keyword).append("\r\n");
|
||||
} else if (keyword.startsWith("Strive") || keyword.startsWith("Escalate")
|
||||
|| keyword.startsWith("ETBReplacement")
|
||||
|| keyword.startsWith("CantBeBlockedBy ")
|
||||
|| keyword.startsWith("Affinity")
|
||||
|| keyword.equals("CARDNAME enters the battlefield tapped.")
|
||||
|| keyword.startsWith("UpkeepCost")) {
|
||||
} else if (keyword.equals("Provoke") || keyword.equals("Ingest") || keyword.equals("Unleash")
|
||||
|| keyword.equals("Soulbond") || keyword.equals("Partner") || keyword.equals("Retrace")
|
||||
|| keyword.equals("Living Weapon") || keyword.equals("Myriad") || keyword.equals("Exploit")
|
||||
|| keyword.equals("Changeling") || keyword.equals("Delve")
|
||||
|| keyword.equals("Split second") || keyword.equals("Sunburst")
|
||||
|| keyword.equals("Suspend") // for the ones without amounnt
|
||||
|| keyword.equals("Hideaway") || keyword.equals("Ascend")
|
||||
|| keyword.equals("Totem armor") || keyword.equals("Battle cry")
|
||||
|| keyword.equals("Devoid") || keyword.equals("Riot")){
|
||||
sbLong.append(keyword + " (" + inst.getReminderText() + ")");
|
||||
} else if (keyword.startsWith("Partner:")) {
|
||||
final String[] k = keyword.split(":");
|
||||
sbLong.append(k.length > 1 ? k[1] + ".\r\n" : "");
|
||||
sbLong.append("Partner with " + k[1] + " (" + inst.getReminderText() + ")");
|
||||
} else if (keyword.startsWith("Modular") || keyword.startsWith("Bloodthirst") || keyword.startsWith("Dredge")
|
||||
|| keyword.startsWith("Fabricate") || keyword.startsWith("Soulshift") || keyword.startsWith("Bushido")
|
||||
|| keyword.startsWith("Crew") || keyword.startsWith("Tribute") || keyword.startsWith("Absorb")
|
||||
|| keyword.startsWith("Graft") || keyword.startsWith("Fading") || keyword.startsWith("Vanishing")
|
||||
|| keyword.startsWith("Afterlife")
|
||||
|| keyword.startsWith("Afflict") || keyword.startsWith ("Poisonous") || keyword.startsWith("Rampage")
|
||||
|| keyword.startsWith("Renown") || keyword.startsWith("Annihilator") || keyword.startsWith("Devour")) {
|
||||
final String[] k = keyword.split(":");
|
||||
sbLong.append(k[0] + " " + k[1] + " (" + inst.getReminderText() + ")");
|
||||
} else if (keyword.contains("Haunt")) {
|
||||
sb.append("\r\nHaunt (");
|
||||
if (isCreature()) {
|
||||
sb.append("When this creature dies, exile it haunting target creature.");
|
||||
} else {
|
||||
sb.append("When this spell card is put into a graveyard after resolving, ");
|
||||
sb.append("exile it haunting target creature.");
|
||||
}
|
||||
sb.append(")");
|
||||
} else if (keyword.equals("Convoke") || keyword.equals("Dethrone")|| keyword.equals("Fear")
|
||||
|| keyword.equals("Melee") || keyword.equals("Improvise")|| keyword.equals("Shroud")
|
||||
|| keyword.equals("Banding") || keyword.equals("Intimidate")|| keyword.equals("Evolve")
|
||||
|| keyword.equals("Exalted") || keyword.equals("Extort")|| keyword.equals("Flanking")
|
||||
|| keyword.equals("Horsemanship") || keyword.equals("Infect")|| keyword.equals("Persist")
|
||||
|| keyword.equals("Phasing") || keyword.equals("Shadow")|| keyword.equals("Skulk")
|
||||
|| keyword.equals("Undying") || keyword.equals("Wither") || keyword.equals("Cascade")
|
||||
|| keyword.equals("Mentor")) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append("\r\n");
|
||||
}
|
||||
sb.append(keyword + " (" + inst.getReminderText() + ")");
|
||||
} else if (keyword.endsWith(" offering")) {
|
||||
String offeringType = keyword.split(" ")[0];
|
||||
if (sb.length() != 0) {
|
||||
sb.append("\r\n");
|
||||
}
|
||||
sbLong.append(keyword);
|
||||
sbLong.append(" (" + Keyword.getInstance("Offering:"+ offeringType).getReminderText() + ")");
|
||||
} else if (keyword.startsWith("Equip") || keyword.startsWith("Fortify") || keyword.startsWith("Outlast")
|
||||
|| keyword.startsWith("Unearth") || keyword.startsWith("Scavenge") || keyword.startsWith("Spectacle")
|
||||
|| keyword.startsWith("Evoke") || keyword.startsWith("Bestow") || keyword.startsWith("Dash")
|
||||
|| keyword.startsWith("Surge") || keyword.startsWith("Transmute") || keyword.startsWith("Suspend")
|
||||
|| keyword.equals("Undaunted") || keyword.startsWith("Monstrosity") || keyword.startsWith("Embalm")
|
||||
|| keyword.startsWith("Level up") || keyword.equals("Prowess") || keyword.startsWith("Eternalize")
|
||||
|| keyword.startsWith("Reinforce") || keyword.startsWith("Champion") || keyword.startsWith("Prowl")
|
||||
|| keyword.startsWith("Amplify") || keyword.startsWith("Ninjutsu") || keyword.startsWith("Adapt")
|
||||
|| keyword.startsWith("Transfigure") || keyword.startsWith("Aura swap")
|
||||
|| keyword.startsWith("Cycling") || keyword.startsWith("TypeCycling")) {
|
||||
// keyword parsing takes care of adding a proper description
|
||||
} else if (keyword.startsWith("CantBeBlockedByAmount")) {
|
||||
sbLong.append(getName()).append(" can't be blocked ");
|
||||
sbLong.append(getTextForKwCantBeBlockedByAmount(keyword));
|
||||
} else if (keyword.startsWith("CantBlock")) {
|
||||
sbLong.append(getName()).append(" can't block ");
|
||||
if (keyword.contains("CardUID")) {
|
||||
sbLong.append("CardID (").append(Integer.valueOf(keyword.split("CantBlockCardUID_")[1])).append(")");
|
||||
} else {
|
||||
final String[] k = keyword.split(":");
|
||||
sbLong.append(k.length > 1 ? k[1] + ".\r\n" : "");
|
||||
}
|
||||
} else if (keyword.equals("Unblockable")) {
|
||||
sbLong.append(getName()).append(" can't be blocked.\r\n");
|
||||
} else if (keyword.equals("AllNonLegendaryCreatureNames")) {
|
||||
sbLong.append(getName()).append(" has all names of nonlegendary creature cards.\r\n");
|
||||
} else if (keyword.startsWith("IfReach")) {
|
||||
String k[] = keyword.split(":");
|
||||
sbLong.append(getName()).append(" can block ")
|
||||
.append(CardType.getPluralType(k[1]))
|
||||
.append(" as though it had reach.\r\n");
|
||||
} else if (keyword.startsWith("MayEffectFromOpeningHand")) {
|
||||
final String[] k = keyword.split(":");
|
||||
// need to get SpellDescription from Svar
|
||||
String desc = AbilityFactory.getMapParams(getSVar(k[1])).get("SpellDescription");
|
||||
sbLong.append(desc);
|
||||
} else if (keyword.startsWith("Saga")) {
|
||||
String k[] = keyword.split(":");
|
||||
String desc = "(As this Saga enters and after your draw step, "
|
||||
+ " add a lore counter. Sacrifice after " + Strings.repeat("I", Integer.valueOf(k[1])) + ".)";
|
||||
sbLong.append(desc);
|
||||
}
|
||||
} else if (keyword.equals("Unblockable")) {
|
||||
sbLong.append(getName()).append(" can't be blocked.\r\n");
|
||||
} else if (keyword.equals("AllNonLegendaryCreatureNames")) {
|
||||
sbLong.append(getName()).append(" has all names of nonlegendary creature cards.\r\n");
|
||||
} else if (keyword.startsWith("IfReach")) {
|
||||
String k[] = keyword.split(":");
|
||||
sbLong.append(getName()).append(" can block ")
|
||||
.append(CardType.getPluralType(k[1]))
|
||||
.append(" as though it had reach.\r\n");
|
||||
} else if (keyword.startsWith("MayEffectFromOpeningHand")) {
|
||||
final String[] k = keyword.split(":");
|
||||
// need to get SpellDescription from Svar
|
||||
String desc = AbilityFactory.getMapParams(getSVar(k[1])).get("SpellDescription");
|
||||
sbLong.append(desc);
|
||||
} else if (keyword.startsWith("Saga")) {
|
||||
String k[] = keyword.split(":");
|
||||
String desc = "(As this Saga enters and after your draw step, "
|
||||
+ " add a lore counter. Sacrifice after " + Strings.repeat("I", Integer.valueOf(k[1])) + ".)";
|
||||
sbLong.append(desc);
|
||||
}
|
||||
else {
|
||||
if ((i != 0) && (sb.length() != 0)) {
|
||||
sb.append(", ");
|
||||
else {
|
||||
if ((i != 0) && (sb.length() != 0)) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(keyword);
|
||||
}
|
||||
if (sbLong.length() > 0) {
|
||||
sbLong.append("\r\n");
|
||||
}
|
||||
sb.append(keyword);
|
||||
}
|
||||
if (sbLong.length() > 0) {
|
||||
sbLong.append("\r\n");
|
||||
}
|
||||
|
||||
i++;
|
||||
i++;
|
||||
} catch (Exception e) {
|
||||
String msg = "Card:keywordToText: crash in Keyword parsing";
|
||||
Sentry.getContext().recordBreadcrumb(
|
||||
new BreadcrumbBuilder().setMessage(msg)
|
||||
.withData("Card", this.getName()).withData("Keyword", keyword).build()
|
||||
);
|
||||
|
||||
throw new RuntimeException("Error in Card " + this.getName() + " with Keyword " + keyword, e);
|
||||
}
|
||||
}
|
||||
if (sb.length() > 0) {
|
||||
sb.append("\r\n");
|
||||
@@ -2249,6 +2246,16 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
updateBasicLandAbilities(list, state);
|
||||
}
|
||||
|
||||
// add Facedown abilities from Original state but only if this state is face down
|
||||
// need CardStateView#getState or might crash in StackOverflow
|
||||
if ((mana == null || mana == false) && isFaceDown() && state.getView().getState() == CardStateName.FaceDown) {
|
||||
for (SpellAbility sa : getState(CardStateName.Original).getNonManaAbilities()) {
|
||||
if (sa.isManifestUp() || sa.isMorphUp()) {
|
||||
list.add(sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (KeywordInterface kw : getUnhiddenKeywords(state)) {
|
||||
for (SpellAbility sa : kw.getAbilities()) {
|
||||
if (mana == null || mana == sa.isManaAbility()) {
|
||||
@@ -3232,11 +3239,15 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
}
|
||||
|
||||
public final void tap() {
|
||||
tap(false);
|
||||
}
|
||||
public final void tap(boolean attacker) {
|
||||
if (tapped) { return; }
|
||||
|
||||
// Run triggers
|
||||
final Map<String, Object> runParams = Maps.newTreeMap();
|
||||
runParams.put("Card", this);
|
||||
runParams.put("Attacker", attacker);
|
||||
getGame().getTriggerHandler().runTrigger(TriggerType.Taps, runParams, false);
|
||||
|
||||
setTapped(true);
|
||||
@@ -3395,6 +3406,15 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
return change;
|
||||
}
|
||||
|
||||
public final boolean hasChangedCardKeywords(final long timestamp) {
|
||||
return changedCardKeywords.containsKey(timestamp);
|
||||
}
|
||||
|
||||
public final void addChangedCardKeywordsInternal(final KeywordsChange change, final long timestamp) {
|
||||
changedCardKeywords.put(timestamp, change);
|
||||
updateKeywordsCache(currentState);
|
||||
}
|
||||
|
||||
// Hidden keywords will be left out
|
||||
public final Collection<KeywordInterface> getUnhiddenKeywords() {
|
||||
return getUnhiddenKeywords(currentState);
|
||||
@@ -3643,10 +3663,8 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
if (s.startsWith("HIDDEN")) {
|
||||
removeHiddenExtrinsicKeyword(s);
|
||||
}
|
||||
else {
|
||||
if (extrinsicKeyword.remove(s)) {
|
||||
currentState.getView().updateKeywords(this, currentState);
|
||||
}
|
||||
else if (extrinsicKeyword.remove(s)) {
|
||||
currentState.getView().updateKeywords(this, currentState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4812,6 +4830,15 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
// Note: This should only be called after state has been set to CardStateName.FaceDown,
|
||||
// so the below call should be valid since the state should have been created already.
|
||||
getState(CardStateName.FaceDown).setImageKey(ImageKeys.getTokenKey(image));
|
||||
if (!manifested) {
|
||||
// remove Manifest Up abilities from Original State
|
||||
CardState original = getState(CardStateName.Original);
|
||||
for (SpellAbility sa : original.getNonManaAbilities()) {
|
||||
if (sa.isManifestUp()) {
|
||||
original.removeSpellAbility(sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final void animateBestow() {
|
||||
@@ -5534,19 +5561,48 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
}
|
||||
abilities.removeAll(toRemove);
|
||||
|
||||
if (getState(CardStateName.Original).getType().isLand()) {
|
||||
if (getState(CardStateName.Original).getType().isLand() && !getLastKnownZone().is(ZoneType.Battlefield)) {
|
||||
LandAbility la = new LandAbility(this, player, null);
|
||||
if (la.canPlay()) {
|
||||
abilities.add(la);
|
||||
}
|
||||
|
||||
Card source = this;
|
||||
boolean lkicheck = false;
|
||||
|
||||
// if Card is Facedown, need to check if MayPlay still applies
|
||||
if (isFaceDown()) {
|
||||
lkicheck = true;
|
||||
source = CardUtil.getLKICopy(source);
|
||||
|
||||
// TODO need to be changed with CloneRewrite and FaceDownState?
|
||||
source.turnFaceUp(false, false);
|
||||
source.getCurrentState().copyFrom(getState(CardStateName.Original), true);
|
||||
}
|
||||
|
||||
if (lkicheck) {
|
||||
// double freeze tracker, so it doesn't update view
|
||||
game.getTracker().freeze();
|
||||
CardCollection preList = new CardCollection(source);
|
||||
game.getAction().checkStaticAbilities(false, Sets.newHashSet(source), preList);
|
||||
}
|
||||
|
||||
// extra for MayPlay
|
||||
for (CardPlayOption o : this.mayPlay(player)) {
|
||||
for (CardPlayOption o : source.mayPlay(player)) {
|
||||
la = new LandAbility(this, player, o.getAbility());
|
||||
if (la.canPlay()) {
|
||||
abilities.add(la);
|
||||
}
|
||||
}
|
||||
|
||||
// reset static abilities
|
||||
if (lkicheck) {
|
||||
game.getAction().checkStaticAbilities(false);
|
||||
// clear delayed changes, this check should not have updated the view
|
||||
game.getTracker().clearDelayed();
|
||||
// need to unfreeze tracker
|
||||
game.getTracker().unfreeze();
|
||||
}
|
||||
}
|
||||
|
||||
return abilities;
|
||||
@@ -5715,7 +5771,7 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
public void setChangedCardKeywords(Map<Long, KeywordsChange> changedCardKeywords) {
|
||||
this.changedCardKeywords.clear();
|
||||
for (Entry<Long, KeywordsChange> entry : changedCardKeywords.entrySet()) {
|
||||
this.changedCardKeywords.put(entry.getKey(), entry.getValue());
|
||||
this.changedCardKeywords.put(entry.getKey(), entry.getValue().copy(this, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,13 @@ import forge.game.trigger.TriggerType;
|
||||
public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
|
||||
private Table<Card, GameEntity, Integer> dataMap = HashBasedTable.create();
|
||||
|
||||
public CardDamageMap(Table<Card, GameEntity, Integer> damageMap) {
|
||||
this.putAll(damageMap);
|
||||
}
|
||||
|
||||
public CardDamageMap() {
|
||||
}
|
||||
|
||||
public void triggerPreventDamage(boolean isCombat) {
|
||||
for (Map.Entry<GameEntity, Map<Card, Integer>> e : this.columnMap().entrySet()) {
|
||||
int sum = 0;
|
||||
|
||||
@@ -25,8 +25,6 @@ import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementHandler;
|
||||
@@ -95,7 +93,6 @@ public class CardFactory {
|
||||
out.setAttachedCards(in.getAttachedCards());
|
||||
out.setEntityAttachedTo(in.getEntityAttachedTo());
|
||||
|
||||
out.setClones(in.getClones());
|
||||
out.setCastSA(in.getCastSA());
|
||||
for (final Object o : in.getRemembered()) {
|
||||
out.addRemembered(o);
|
||||
@@ -431,6 +428,9 @@ public class CardFactory {
|
||||
|
||||
private static void readCardFace(Card c, ICardFace face) {
|
||||
|
||||
// Name first so Senty has the Card name
|
||||
c.setName(face.getName());
|
||||
|
||||
for (String r : face.getReplacements()) c.addReplacementEffect(ReplacementHandler.parseReplacement(r, c, true));
|
||||
for (String s : face.getStaticAbilities()) c.addStaticAbility(s);
|
||||
for (String t : face.getTriggers()) c.addTrigger(TriggerHandler.parseTrigger(t, c, true));
|
||||
@@ -440,7 +440,6 @@ public class CardFactory {
|
||||
// keywords not before variables
|
||||
c.addIntrinsicKeywords(face.getKeywords(), false);
|
||||
|
||||
c.setName(face.getName());
|
||||
c.setManaCost(face.getManaCost());
|
||||
c.setText(face.getNonAbilityText());
|
||||
|
||||
@@ -618,6 +617,9 @@ public class CardFactory {
|
||||
}
|
||||
if (from.getRestrictions() != null) {
|
||||
to.setRestrictions((SpellAbilityRestriction) from.getRestrictions().copy());
|
||||
if (!lki) {
|
||||
to.getRestrictions().resetTurnActivations();
|
||||
}
|
||||
}
|
||||
if (from.getConditions() != null) {
|
||||
to.setConditions((SpellAbilityCondition) from.getConditions().copy());
|
||||
@@ -676,9 +678,6 @@ public class CardFactory {
|
||||
}
|
||||
|
||||
trig.setStackDescription(trig.toString());
|
||||
if (trig.getApi() == ApiType.Charm && !trig.isWrapper()) {
|
||||
CharmEffect.makeChoices(trig);
|
||||
}
|
||||
|
||||
WrappedAbility wrapperAbility = new WrappedAbility(t, trig, ((WrappedAbility) sa).getDecider());
|
||||
wrapperAbility.setTrigger(true);
|
||||
|
||||
@@ -25,7 +25,6 @@ import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import forge.GameCommand;
|
||||
import forge.card.*;
|
||||
import forge.card.mana.ManaAtom;
|
||||
import forge.card.mana.ManaCost;
|
||||
@@ -136,27 +135,27 @@ public class CardFactoryUtil {
|
||||
public static SpellAbility abilityMorphUp(final Card sourceCard, final String costStr, final boolean mega) {
|
||||
Cost cost = new Cost(costStr, true);
|
||||
String costDesc = cost.toString();
|
||||
// get rid of the ": " at the end
|
||||
costDesc = costDesc.substring(0, costDesc.length() - 2);
|
||||
|
||||
StringBuilder sbCost = new StringBuilder(mega ? "Megamorph" : "Morph");
|
||||
sbCost.append(" ");
|
||||
if (!cost.isOnlyManaCost()) {
|
||||
costDesc = "—" + costDesc;
|
||||
sbCost.append("— ");
|
||||
}
|
||||
// get rid of the ": " at the end
|
||||
sbCost.append(costDesc.substring(0, costDesc.length() - 2));
|
||||
|
||||
String ab = "ST$ SetState | Cost$ " + costStr + " | CostDesc$ Morph" + costDesc
|
||||
+ " | MorphUp$ True"
|
||||
+ " | ConditionDefined$ Self | ConditionPresent$ Card.faceDown"
|
||||
+ " | Mode$ TurnFace | SpellDescription$ (Turn this face up any time for its morph cost.)";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("ST$ SetState | Cost$ ").append(costStr).append(" | CostDesc$ ").append(sbCost);
|
||||
sb.append(" | MorphUp$ True | Secondary$ True | IsPresent$ Card.Self+faceDown");
|
||||
if (mega) {
|
||||
ab += " | Mega$ True";
|
||||
sb.append(" | Mega$ True");
|
||||
}
|
||||
sb.append(" | Mode$ TurnFace | SpellDescription$ (Turn this face up any time for its morph cost.)");
|
||||
|
||||
final SpellAbility morphUp = AbilityFactory.getAbility(ab, sourceCard);
|
||||
final SpellAbility morphUp = AbilityFactory.getAbility(sb.toString(), sourceCard);
|
||||
|
||||
final StringBuilder sbStack = new StringBuilder();
|
||||
sbStack.append(sourceCard.getName()).append(" - turn this card face up.");
|
||||
morphUp.setStackDescription(sbStack.toString());
|
||||
morphUp.setIsMorphUp(true);
|
||||
|
||||
return morphUp;
|
||||
}
|
||||
@@ -166,18 +165,17 @@ public class CardFactoryUtil {
|
||||
String costDesc = manaCost.toString();
|
||||
|
||||
// Cost need to be set later
|
||||
String ab = "ST$ SetState | Cost$ 0 | CostDesc$ Unmanifest " + costDesc
|
||||
+ " | ManifestUp$ True"
|
||||
+ " | ConditionDefined$ Self | ConditionPresent$ Card.faceDown+manifested"
|
||||
+ " | Mode$ TurnFace | SpellDescription$ (Turn this face up any time for its mana cost.)";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("ST$ SetState | Cost$ 0 | CostDesc$ Unmanifest ").append(costDesc);
|
||||
sb.append(" | ManifestUp$ True | Secondary$ True | IsPresent$ Card.Self+faceDown+manifested");
|
||||
sb.append(" | Mode$ TurnFace | SpellDescription$ (Turn this face up any time for its mana cost.)");
|
||||
|
||||
final SpellAbility manifestUp = AbilityFactory.getAbility(ab, sourceCard);
|
||||
final SpellAbility manifestUp = AbilityFactory.getAbility(sb.toString(), sourceCard);
|
||||
manifestUp.setPayCosts(new Cost(manaCost, true));
|
||||
|
||||
final StringBuilder sbStack = new StringBuilder();
|
||||
sbStack.append(sourceCard.getName()).append(" - turn this card face up.");
|
||||
manifestUp.setStackDescription(sbStack.toString());
|
||||
manifestUp.setIsManifestUp(true);
|
||||
|
||||
return manifestUp;
|
||||
}
|
||||
@@ -1238,10 +1236,10 @@ public class CardFactoryUtil {
|
||||
return doXMath(c.getPseudoKickerMagnitude(), m, c);
|
||||
}
|
||||
|
||||
// Count$IfMainPhase.<numMain>.<numNotMain> // 7/10
|
||||
if (sq[0].contains("IfMainPhase")) {
|
||||
// Count$IfCastInOwnMainPhase.<numMain>.<numNotMain> // 7/10
|
||||
if (sq[0].contains("IfCastInOwnMainPhase")) {
|
||||
final PhaseHandler cPhase = cc.getGame().getPhaseHandler();
|
||||
final boolean isMyMain = cPhase.getPhase().isMain() && cPhase.getPlayerTurn().equals(cc);
|
||||
final boolean isMyMain = cPhase.getPhase().isMain() && cPhase.getPlayerTurn().equals(cc) && c.getCastFrom() != null;
|
||||
return doXMath(Integer.parseInt(sq[isMyMain ? 1 : 2]), m, c);
|
||||
}
|
||||
|
||||
@@ -2100,7 +2098,7 @@ public class CardFactoryUtil {
|
||||
String abStr = "DB$ PutCounter | Defined$ Self | CounterType$ " + splitkw[1]
|
||||
+ " | ETB$ True | CounterNum$ " + amount;
|
||||
|
||||
if (!StringUtils.isNumeric(amount)) {
|
||||
if (!StringUtils.isNumeric(amount) && card.hasSVar(amount)) {
|
||||
abStr += " | References$ " + amount;
|
||||
}
|
||||
|
||||
@@ -2433,9 +2431,8 @@ public class CardFactoryUtil {
|
||||
final String counter = "DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ " + n +
|
||||
" | IsPresent$ Card.StrictlySelf | SpellDescription$ Put "
|
||||
+ Lang.nounWithNumeral(n, "+1/+1 counter") + " on it.";
|
||||
final String token = "DB$ Token | TokenAmount$ " + n + " | TokenName$ Servo | TokenTypes$ Artifact,Creature,Servo"
|
||||
+ " | TokenOwner$ You | TokenColors$ Colorless | TokenPower$ 1 | TokenToughness$ 1"
|
||||
+ " | TokenImage$ c 1 1 servo | TokenAltImages$ c_1_1_servo2,c_1_1_servo3 | SpellDescription$ Create "
|
||||
final String token = "DB$ Token | TokenAmount$ " + n + " | TokenScript$ c_1_1_a_servo | TokenOwner$ You "
|
||||
+ " | LegacyImage$ c 1 1 a servo aer | SpellDescription$ Create "
|
||||
+ Lang.nounWithNumeral(n, "1/1 colorless Servo artifact creature token") + ".";
|
||||
|
||||
final Trigger trigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic);
|
||||
@@ -2633,8 +2630,7 @@ public class CardFactoryUtil {
|
||||
sbTrig.append("Living Weapon (" + inst.getReminderText() + ")");
|
||||
|
||||
final StringBuilder sbGerm = new StringBuilder();
|
||||
sbGerm.append("DB$ Token | TokenAmount$ 1 | TokenName$ Germ | TokenTypes$ Creature,Germ | RememberTokens$ True | ");
|
||||
sbGerm.append("TokenOwner$ You | TokenColors$ Black | TokenPower$ 0 | TokenToughness$ 0 | TokenImage$ B 0 0 Germ");
|
||||
sbGerm.append("DB$ Token | TokenAmount$ 1 | TokenScript$ b_0_0_germ |TokenOwner$ You | RememberTokens$ True");
|
||||
|
||||
final SpellAbility saGerm = AbilityFactory.getAbility(sbGerm.toString(), card);
|
||||
|
||||
@@ -3453,12 +3449,10 @@ public class CardFactoryUtil {
|
||||
sb.append(m);
|
||||
sb.append(" (").append(inst.getReminderText()).append(")");
|
||||
|
||||
if ("Sunburst".equals(m)) {
|
||||
card.setSVar(m, "Count$Converge");
|
||||
}
|
||||
|
||||
final ReplacementEffect re = makeEtbCounter(sb.toString(), card, intrinsic);
|
||||
|
||||
if ("Sunburst".equals(m)) {
|
||||
re.getOverridingAbility().setSVar("Sunburst", "Count$Converge");
|
||||
}
|
||||
inst.addReplacement(re);
|
||||
} else if (keyword.equals("Rebound")) {
|
||||
String repeffstr = "Event$ Moved | ValidCard$ Card.Self+wasCastFromHand+YouOwn+YouCtrl "
|
||||
@@ -3513,6 +3507,19 @@ public class CardFactoryUtil {
|
||||
String sb = "etbCounter:LORE:1:no Condition:no desc";
|
||||
final ReplacementEffect re = makeEtbCounter(sb, card, intrinsic);
|
||||
|
||||
inst.addReplacement(re);
|
||||
} else if (keyword.equals("Sunburst")) {
|
||||
// Rule 702.43a If this object is entering the battlefield as a creature,
|
||||
// ignoring any type-changing effects that would affect it
|
||||
CounterType t = card.isCreature() ? CounterType.P1P1 : CounterType.CHARGE;
|
||||
|
||||
StringBuilder sb = new StringBuilder("etbCounter:");
|
||||
sb.append(t).append(":Sunburst:no Condition:");
|
||||
sb.append("Sunburst (").append(inst.getReminderText()).append(")");
|
||||
|
||||
final ReplacementEffect re = makeEtbCounter(sb.toString(), card, intrinsic);
|
||||
re.getOverridingAbility().setSVar("Sunburst", "Count$Converge");
|
||||
|
||||
inst.addReplacement(re);
|
||||
} else if (keyword.equals("Totem armor")) {
|
||||
String repeffstr = "Event$ Destroy | ActiveZones$ Battlefield | ValidCard$ Card.EnchantedBy"
|
||||
@@ -3704,13 +3711,24 @@ public class CardFactoryUtil {
|
||||
final String[] k = keyword.split(":");
|
||||
final String magnitude = k[1];
|
||||
final String manacost = k[2];
|
||||
final String reduceCost = k.length > 3 ? k[3] : null;
|
||||
|
||||
Set<String> references = Sets.newHashSet();
|
||||
|
||||
String desc = "Adapt " + magnitude;
|
||||
|
||||
String effect = "AB$ PutCounter | Cost$ " + manacost + " | ConditionPresent$ "
|
||||
+ "Card.Self+counters_EQ0_P1P1 | Adapt$ True | CounterNum$ " + magnitude
|
||||
String effect = "AB$ PutCounter | Cost$ " + manacost + " | Adapt$ True | CounterNum$ " + magnitude
|
||||
+ " | CounterType$ P1P1 | StackDescription$ SpellDescription";
|
||||
|
||||
if (reduceCost != null) {
|
||||
effect += "| ReduceCost$ " + reduceCost;
|
||||
references.add(reduceCost);
|
||||
desc += ". This ability costs {1} less to activate for each instant and sorcery card in your graveyard.";
|
||||
}
|
||||
if (!references.isEmpty()) {
|
||||
effect += "| References$ " + TextUtil.join(references, ",");
|
||||
}
|
||||
|
||||
effect += "| SpellDescription$ " + desc + " (" + inst.getReminderText() + ")";
|
||||
|
||||
final SpellAbility sa = AbilityFactory.getAbility(effect, card);
|
||||
@@ -3726,6 +3744,19 @@ public class CardFactoryUtil {
|
||||
origSA.setAftermath(true);
|
||||
origSA.getRestrictions().setZone(ZoneType.Graveyard);
|
||||
// The Exile part is done by the System itself
|
||||
} else if (keyword.startsWith("Aura swap")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final String manacost = k[1];
|
||||
|
||||
final String effect = "AB$ ExchangeZone | Cost$ " + manacost + " | Zone2$ Hand | Type$ Aura "
|
||||
+ " | PrecostDesc$ Aura swap | CostDesc$ " + ManaCostParser.parse(manacost)
|
||||
+ " | StackDescription$ SpellDescription | SpellDescription$ (" + inst.getReminderText() + ")";
|
||||
|
||||
final SpellAbility sa = AbilityFactory.getAbility(effect, card);
|
||||
sa.setIntrinsic(intrinsic);
|
||||
|
||||
sa.setTemporary(!intrinsic);
|
||||
inst.addSpellAbility(sa);
|
||||
} else if (keyword.startsWith("Awaken")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final String counters = k[1];
|
||||
@@ -4037,22 +4068,12 @@ public class CardFactoryUtil {
|
||||
final String[] k = keyword.split(":");
|
||||
|
||||
inst.addSpellAbility(abilityMorphDown(card));
|
||||
|
||||
CardState state = card.getState(CardStateName.FaceDown);
|
||||
state.setSVars(card.getSVars());
|
||||
KeywordInterface facedownKeyword = Keyword.getInstance("");
|
||||
facedownKeyword.addSpellAbility(abilityMorphUp(card, k[1], false));
|
||||
state.addIntrinsicKeywords(Lists.newArrayList(facedownKeyword));
|
||||
inst.addSpellAbility(abilityMorphUp(card, k[1], false));
|
||||
} else if (keyword.startsWith("Megamorph")){
|
||||
final String[] k = keyword.split(":");
|
||||
|
||||
inst.addSpellAbility(abilityMorphDown(card));
|
||||
|
||||
CardState state = card.getState(CardStateName.FaceDown);
|
||||
state.setSVars(card.getSVars());
|
||||
KeywordInterface facedownKeyword = Keyword.getInstance("");
|
||||
facedownKeyword.addSpellAbility(abilityMorphUp(card, k[1], true));
|
||||
state.addIntrinsicKeywords(Lists.newArrayList(facedownKeyword));
|
||||
inst.addSpellAbility(abilityMorphUp(card, k[1], true));
|
||||
} else if (keyword.startsWith("Multikicker")) {
|
||||
final String[] n = keyword.split(":");
|
||||
final SpellAbility sa = card.getFirstSpellAbility();
|
||||
@@ -4182,18 +4203,17 @@ public class CardFactoryUtil {
|
||||
|
||||
String effect = "AB$ PutCounter | Cost$ " + manacost + " ExileFromGrave<1/CARDNAME> " +
|
||||
"| ActivationZone$ Graveyard | ValidTgts$ Creature | CounterType$ P1P1 " +
|
||||
"| CounterNum$ ScavengeX | SorcerySpeed$ True | References$ ScavengeX " +
|
||||
"| CounterNum$ ScavengeX | SorcerySpeed$ True " +
|
||||
"| PrecostDesc$ Scavenge | CostDesc$ " + ManaCostParser.parse(manacost) +
|
||||
"| SpellDescription$ (" + inst.getReminderText() + ")";
|
||||
|
||||
card.setSVar("ScavengeX", "Count$CardPower");
|
||||
|
||||
final SpellAbility sa = AbilityFactory.getAbility(effect, card);
|
||||
sa.setSVar("ScavengeX", "Count$CardPower");
|
||||
sa.setIntrinsic(intrinsic);
|
||||
|
||||
sa.setTemporary(!intrinsic);
|
||||
inst.addSpellAbility(sa);
|
||||
|
||||
|
||||
} else if (keyword.startsWith("Spectacle")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final Cost cost = new Cost(k[1], false);
|
||||
@@ -4211,28 +4231,6 @@ public class CardFactoryUtil {
|
||||
newSA.setTemporary(!intrinsic);
|
||||
inst.addSpellAbility(newSA);
|
||||
|
||||
} else if (keyword.equals("Sunburst") && intrinsic) {
|
||||
final GameCommand sunburstCIP = new GameCommand() {
|
||||
private static final long serialVersionUID = 1489845860231758299L;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
CounterType t = card.isCreature() ? CounterType.P1P1 : CounterType.CHARGE;
|
||||
card.addCounter(t, card.getSunburstValue(), card.getController(), true);
|
||||
}
|
||||
};
|
||||
|
||||
final GameCommand sunburstLP = new GameCommand() {
|
||||
private static final long serialVersionUID = -7564420917490677427L;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
card.setSunburstValue(0);
|
||||
}
|
||||
};
|
||||
|
||||
card.addComesIntoPlayCommand(sunburstCIP);
|
||||
card.addLeavesPlayCommand(sunburstLP);
|
||||
} else if (keyword.startsWith("Surge")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final Cost surgeCost = new Cost(k[1], false);
|
||||
@@ -4299,17 +4297,33 @@ public class CardFactoryUtil {
|
||||
|
||||
suspend.setTemporary(!intrinsic);
|
||||
inst.addSpellAbility(suspend);
|
||||
} else if (keyword.startsWith("Transfigure")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final String manacost = k[1];
|
||||
final String effect = "AB$ ChangeZone | Cost$ " + manacost + " Sac<1/CARDNAME>"
|
||||
+ " | PrecostDesc$ Transfigure | CostDesc$ " + ManaCostParser.parse(manacost)
|
||||
+ " | Origin$ Library | Destination$ Battlefield | ChangeType$ Creature.cmcEQTransfigureX"
|
||||
+ " | ChangeNum$ 1 | SorcerySpeed$ True | StackDescription$ SpellDescription | SpellDescription$ ("
|
||||
+ inst.getReminderText() + ")";
|
||||
|
||||
final SpellAbility sa = AbilityFactory.getAbility(effect, card);
|
||||
sa.setSVar("TransfigureX", "Count$CardManaCost");
|
||||
sa.setIntrinsic(intrinsic);
|
||||
|
||||
sa.setTemporary(!intrinsic);
|
||||
inst.addSpellAbility(sa);
|
||||
} else if (keyword.startsWith("Transmute")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final String manacost = k[1];
|
||||
|
||||
final String effect = "AB$ ChangeZone | Cost$ " + manacost + " Discard<1/CARDNAME>"
|
||||
+ " | PrecostDesc$ Transmute | CostDesc$ " + ManaCostParser.parse(manacost) + " | ActivationZone$ Hand"
|
||||
+ " | Origin$ Library | Destination$ Hand | ChangeType$ Card.cmcEQ" + card.getManaCost().getCMC()
|
||||
+ " | Origin$ Library | Destination$ Hand | ChangeType$ Card.cmcEQTransmuteX"
|
||||
+ " | ChangeNum$ 1 | SorcerySpeed$ True | StackDescription$ SpellDescription | SpellDescription$ ("
|
||||
+ inst.getReminderText() + ")";
|
||||
|
||||
final SpellAbility sa = AbilityFactory.getAbility(effect, card);
|
||||
sa.setSVar("TransmuteX", "Count$CardManaCost");
|
||||
sa.setIntrinsic(intrinsic);
|
||||
|
||||
sa.setTemporary(!intrinsic);
|
||||
|
||||
@@ -967,6 +967,19 @@ public class CardProperty {
|
||||
if (restriction.startsWith("Remembered") || restriction.startsWith("Imprinted")) {
|
||||
CardCollection list = AbilityUtils.getDefinedCards(source, restriction, spellAbility);
|
||||
return CardLists.filter(list, CardPredicates.sharesNameWith(card)).isEmpty();
|
||||
} else if (restriction.equals("YourGraveyard")) {
|
||||
return CardLists.filter(sourceController.getCardsIn(ZoneType.Graveyard), CardPredicates.sharesNameWith(card)).isEmpty();
|
||||
} else if (restriction.equals("OtherYourBattlefield")) {
|
||||
// Obviously it's going to share a name with itself, so consider that in the
|
||||
CardCollection list = CardLists.filter(sourceController.getCardsIn(ZoneType.Battlefield), CardPredicates.sharesNameWith(card));
|
||||
|
||||
if (list.size() == 1) {
|
||||
Card c = list.getFirst();
|
||||
if (c.getTimestamp() == card.getTimestamp() && c.getId() == card.getId()) {
|
||||
list.remove(card);
|
||||
}
|
||||
}
|
||||
return list.isEmpty();
|
||||
}
|
||||
}
|
||||
} else if (property.startsWith("sharesControllerWith")) {
|
||||
@@ -1239,7 +1252,7 @@ public class CardProperty {
|
||||
} else if (property.startsWith("greatestPower")) {
|
||||
CardCollectionView cards = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
|
||||
if (property.contains("ControlledBy")) {
|
||||
FCollectionView<Player> p = AbilityUtils.getDefinedPlayers(source, property.split("ControlledBy")[1], null);
|
||||
FCollectionView<Player> p = AbilityUtils.getDefinedPlayers(source, property.split("ControlledBy")[1], spellAbility);
|
||||
cards = CardLists.filterControlledBy(cards, p);
|
||||
if (!cards.contains(card)) {
|
||||
return false;
|
||||
|
||||
@@ -43,6 +43,9 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.sentry.Sentry;
|
||||
import io.sentry.event.BreadcrumbBuilder;
|
||||
|
||||
public class CardState extends GameObject {
|
||||
private String name = "";
|
||||
private CardType type = new CardType();
|
||||
@@ -69,6 +72,10 @@ public class CardState extends GameObject {
|
||||
private final CardStateView view;
|
||||
private final Card card;
|
||||
|
||||
public CardState(Card card, CardStateName name) {
|
||||
this(card.getView().createAlternateState(name), card);
|
||||
}
|
||||
|
||||
public CardState(CardStateView view0, Card card0) {
|
||||
view = view0;
|
||||
card = card0;
|
||||
@@ -209,7 +216,19 @@ public class CardState extends GameObject {
|
||||
if (s.trim().length() == 0) {
|
||||
return null;
|
||||
}
|
||||
KeywordInterface inst = intrinsicKeywords.add(s);
|
||||
KeywordInterface inst = null;
|
||||
try {
|
||||
inst = intrinsicKeywords.add(s);
|
||||
} catch (Exception e) {
|
||||
String msg = "CardState:addIntrinsicKeyword: failed to parse Keyword";
|
||||
Sentry.getContext().recordBreadcrumb(
|
||||
new BreadcrumbBuilder().setMessage(msg)
|
||||
.withData("Card", card.getName()).withData("Keyword", s).build()
|
||||
);
|
||||
|
||||
//rethrow
|
||||
throw new RuntimeException("Error in Keyword " + s + " for card " + card.getName(), e);
|
||||
}
|
||||
if (inst != null && initTraits) {
|
||||
inst.createTraits(card, true);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public final class CardUtil {
|
||||
"Transmute", "Replicate", "Recover", "Suspend", "Aura swap",
|
||||
"Fortify", "Transfigure", "Champion", "Evoke", "Prowl", "IfReach",
|
||||
"Reinforce", "Unearth", "Level up", "Miracle", "Overload",
|
||||
"Scavenge", "Bestow", "Outlast", "Dash", "Renown", "Surge", "Emerge", "Hexproof:").build();
|
||||
"Scavenge", "Bestow", "Outlast", "Dash", "Surge", "Emerge", "Hexproof:").build();
|
||||
/** List of keyword endings of keywords that could be modified by text changes. */
|
||||
public static final ImmutableList<String> modifiableKeywordEndings = ImmutableList.<String>builder().add(
|
||||
"walk", "cycling", "offering").build();
|
||||
@@ -269,7 +269,6 @@ public final class CardUtil {
|
||||
newCopy.setAttachedCards(in.getAttachedCards());
|
||||
newCopy.setEntityAttachedTo(in.getEntityAttachedTo());
|
||||
|
||||
newCopy.setClones(in.getClones());
|
||||
newCopy.setHaunting(in.getHaunting());
|
||||
newCopy.setCopiedPermanent(in.getCopiedPermanent());
|
||||
for (final Card haunter : in.getHauntedBy()) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
@@ -45,4 +46,39 @@ public class CardZoneTable extends ForwardingTable<ZoneType, ZoneType, CardColle
|
||||
game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false);
|
||||
}
|
||||
}
|
||||
|
||||
public CardCollection filterCards(Iterable<ZoneType> origin, ZoneType destination, String valid, Card host, SpellAbility sa) {
|
||||
CardCollection allCards = new CardCollection();
|
||||
if (destination != null) {
|
||||
if (!containsColumn(destination)) {
|
||||
return allCards;
|
||||
}
|
||||
}
|
||||
if (origin != null) {
|
||||
for (ZoneType z : origin) {
|
||||
if (containsRow(z)) {
|
||||
if (destination != null) {
|
||||
allCards.addAll(row(z).get(destination));
|
||||
} else {
|
||||
for (CardCollection c : row(z).values()) {
|
||||
allCards.addAll(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (destination != null) {
|
||||
for (CardCollection c : column(destination).values()) {
|
||||
allCards.addAll(c);
|
||||
}
|
||||
} else {
|
||||
for (CardCollection c : values()) {
|
||||
allCards.addAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (valid != null) {
|
||||
allCards = CardLists.getValidCards(allCards, valid.split(","), host.getController(), host, sa);
|
||||
}
|
||||
return allCards;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import forge.StaticData;
|
||||
import forge.card.CardType;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardFactory;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
@@ -17,12 +18,11 @@ import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.item.PaperToken;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class TokenInfo {
|
||||
final String name;
|
||||
final String imageName;
|
||||
@@ -128,8 +128,6 @@ public class TokenInfo {
|
||||
c.setName(name);
|
||||
c.setImageKey(ImageKeys.getTokenKey(imageName));
|
||||
|
||||
// TODO - most tokens mana cost is 0, this needs to be fixed
|
||||
// c.setManaCost(manaCost);
|
||||
c.setColor(color.isEmpty() ? manaCost : color);
|
||||
c.setToken(true);
|
||||
|
||||
@@ -226,6 +224,10 @@ public class TokenInfo {
|
||||
}
|
||||
|
||||
static public Card getProtoType(final String script, final SpellAbility sa) {
|
||||
// script might be null, or sa might be null
|
||||
if (script == null || sa == null) {
|
||||
return null;
|
||||
}
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
|
||||
@@ -235,6 +237,18 @@ public class TokenInfo {
|
||||
if (token != null) {
|
||||
final Card result = Card.fromPaperCard(token, null, game);
|
||||
|
||||
if (sa.hasParam("TokenPower")) {
|
||||
String str = sa.getParam("TokenPower");
|
||||
result.setBasePowerString(str);
|
||||
result.setBasePower(AbilityUtils.calculateAmount(host, str, sa));
|
||||
}
|
||||
|
||||
if (sa.hasParam("TokenToughness")) {
|
||||
String str = sa.getParam("TokenToughness");
|
||||
result.setBaseToughnessString(str);
|
||||
result.setBaseToughness(AbilityUtils.calculateAmount(host, str, sa));
|
||||
}
|
||||
|
||||
// update Token with CardTextChanges
|
||||
Map<String, String> colorMap = sa.getChangedTextColors();
|
||||
Map<String, String> typeMap = sa.getChangedTextTypes();
|
||||
|
||||
@@ -816,6 +816,7 @@ public class Combat {
|
||||
}
|
||||
|
||||
preventMap.triggerPreventDamage(true);
|
||||
preventMap.clear();
|
||||
// This was deeper before, but that resulted in the stack entry acting like before.
|
||||
|
||||
// Run the trigger to deal combat damage once
|
||||
|
||||
@@ -43,7 +43,6 @@ import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollection;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import forge.util.maps.MapToAmount;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.List;
|
||||
@@ -525,7 +524,7 @@ public class CombatUtil {
|
||||
IGNORE_LANDWALK_KEYWORDS[i] = "May be blocked as though it doesn't have " + landwalk + ".";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isUnblockableFromLandwalk(final Card attacker, final Player defendingPlayer) {
|
||||
//May be blocked as though it doesn't have landwalk. (Staff of the Ages)
|
||||
if (attacker.hasKeyword("May be blocked as though it doesn't have landwalk.")) {
|
||||
@@ -534,6 +533,7 @@ public class CombatUtil {
|
||||
|
||||
List<String> walkTypes = Lists.newArrayList();
|
||||
|
||||
// handle basic landwalk and snow basic landwalk
|
||||
for (int i = 0; i < LANDWALK_KEYWORDS.length; i++) {
|
||||
final String basic = MagicColor.Constant.BASIC_LANDS.get(i);
|
||||
final String landwalk = LANDWALK_KEYWORDS[i];
|
||||
@@ -553,19 +553,24 @@ public class CombatUtil {
|
||||
String keyword = inst.getOriginal();
|
||||
if (keyword.equals("Legendary landwalk")) {
|
||||
walkTypes.add("Land.Legendary");
|
||||
} else if (keyword.equals("Desertwalk")) {
|
||||
walkTypes.add("Desert");
|
||||
} else if (keyword.equals("Nonbasic landwalk")) {
|
||||
walkTypes.add("Land.nonBasic");
|
||||
} else if (keyword.equals("Snow landwalk")) {
|
||||
walkTypes.add("Land.Snow");
|
||||
} else if (keyword.endsWith("walk")) {
|
||||
final String landtype = TextUtil.fastReplace(keyword, "walk", "");
|
||||
String landtype = TextUtil.fastReplace(keyword, "walk", "");
|
||||
String valid = landtype;
|
||||
|
||||
// substract Snow type
|
||||
if (landtype.startsWith("Snow ")) {
|
||||
walkTypes.add(landtype.substring(5) + ".Snow");
|
||||
} else if (CardType.isALandType(landtype)) {
|
||||
landtype = landtype.substring(5);
|
||||
valid = landtype + ".Snow";
|
||||
}
|
||||
|
||||
// basic land types are handled before
|
||||
if (CardType.isALandType(landtype) && !CardType.isABasicLandType(landtype)) {
|
||||
if (!walkTypes.contains(landtype)) {
|
||||
walkTypes.add(landtype);
|
||||
walkTypes.add(valid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -575,10 +580,10 @@ public class CombatUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String valid = StringUtils.join(walkTypes, ",");
|
||||
final String[] valid = walkTypes.toArray(new String[0]);
|
||||
final CardCollectionView defendingLands = defendingPlayer.getCardsIn(ZoneType.Battlefield);
|
||||
for (final Card c : defendingLands) {
|
||||
if (c.isValid(valid.split(","), defendingPlayer, attacker, null)) {
|
||||
if (c.isValid(valid, defendingPlayer, attacker, null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,10 +366,9 @@ public class CostAdjustment {
|
||||
if (manaCost.toString().equals("{0}")) {
|
||||
return 0;
|
||||
}
|
||||
final Map<String, String> params = staticAbility.getMapParams();
|
||||
final Card hostCard = staticAbility.getHostCard();
|
||||
final Card card = sa.getHostCard();
|
||||
final String amount = params.get("Amount");
|
||||
final String amount = staticAbility.getParam("Amount");
|
||||
|
||||
if (!checkRequirement(sa, staticAbility)) {
|
||||
return 0;
|
||||
@@ -380,14 +379,16 @@ public class CostAdjustment {
|
||||
value = CardFactoryUtil.xCount(card, hostCard.getSVar(amount));
|
||||
} else if ("Undaunted".equals(amount)) {
|
||||
value = card.getController().getOpponents().size();
|
||||
} else if (staticAbility.hasParam("Relative")) {
|
||||
value = AbilityUtils.calculateAmount(hostCard, amount, sa);
|
||||
} else {
|
||||
value = AbilityUtils.calculateAmount(hostCard, amount, staticAbility);
|
||||
}
|
||||
|
||||
if (!params.containsKey("Cost") && ! params.containsKey("Color")) {
|
||||
if (!staticAbility.hasParam("Cost") && ! staticAbility.hasParam("Color")) {
|
||||
int minMana = 0;
|
||||
if (params.containsKey("MinMana")) {
|
||||
minMana = Integer.valueOf(params.get("MinMana"));
|
||||
if (staticAbility.hasParam("MinMana")) {
|
||||
minMana = Integer.valueOf(staticAbility.getParam("MinMana"));
|
||||
}
|
||||
|
||||
final int maxReduction = Math.max(0, manaCost.getConvertedManaCost() - minMana);
|
||||
@@ -395,7 +396,7 @@ public class CostAdjustment {
|
||||
return Math.min(value, maxReduction);
|
||||
}
|
||||
} else {
|
||||
final String color = params.containsKey("Cost") ? params.get("Cost") : params.get("Color");
|
||||
final String color = staticAbility.getParamOrDefault("Cost", staticAbility.getParam("Color"));
|
||||
int sumGeneric = 0;
|
||||
// might be problematic for wierd hybrid combinations
|
||||
for (final String cost : color.split(" ")) {
|
||||
|
||||
@@ -23,7 +23,7 @@ import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
/**
|
||||
* The Class CostPayLife.
|
||||
* The Class CostDamage.
|
||||
*/
|
||||
public class CostDamage extends CostPart {
|
||||
|
||||
@@ -74,6 +74,8 @@ public class CostDamage extends CostPart {
|
||||
preventMap.triggerPreventDamage(false);
|
||||
damageMap.triggerDamageDoneOnce(false, sa);
|
||||
|
||||
preventMap.clear();
|
||||
damageMap.clear();
|
||||
return decision.c > 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The Class CostPayLife.
|
||||
* The Class CostDraw.
|
||||
*/
|
||||
public class CostDraw extends CostPart {
|
||||
/**
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package forge.game.cost;
|
||||
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
@@ -57,17 +56,6 @@ public class CostPayLife extends CostPart {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see forge.card.cost.CostPart#refund(forge.Card)
|
||||
*/
|
||||
@Override
|
||||
public final void refund(final Card source) {
|
||||
// Really should be activating player
|
||||
source.getController().payLife(this.paidAmount * -1, null);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package forge.game.event;
|
||||
|
||||
import forge.game.player.Player;
|
||||
|
||||
// This special event denotes loss of mana due to phase end
|
||||
public class GameEventManaBurn extends GameEvent {
|
||||
|
||||
public final Player player;
|
||||
public final boolean causedLifeLoss;
|
||||
public final int amount;
|
||||
|
||||
@@ -11,8 +14,9 @@ public class GameEventManaBurn extends GameEvent {
|
||||
* @param dealDamage
|
||||
* @param burn
|
||||
*/
|
||||
public GameEventManaBurn(int burn, boolean dealDamage) {
|
||||
amount = burn;
|
||||
public GameEventManaBurn(Player who, int burn, boolean dealDamage) {
|
||||
player = who;
|
||||
amount = burn;
|
||||
causedLifeLoss = dealDamage;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import java.util.Iterator;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.MultimapBuilder;
|
||||
|
||||
import forge.game.card.Card;
|
||||
|
||||
public class KeywordCollection implements Iterable<String>, Serializable {
|
||||
private static final long serialVersionUID = -2882986558147844702L;
|
||||
|
||||
@@ -151,6 +153,12 @@ public class KeywordCollection implements Iterable<String>, Serializable {
|
||||
return map.get(keyword);
|
||||
}
|
||||
|
||||
public void setHostCard(final Card host) {
|
||||
for (KeywordInterface k : map.values()) {
|
||||
k.setHostCard(host);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return new Iterator<String>() {
|
||||
|
||||
@@ -202,7 +202,7 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
||||
public Collection<StaticAbility> getStaticAbilities() {
|
||||
return staticAbilities;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see forge.game.keyword.KeywordInterface#copy()
|
||||
@@ -233,7 +233,7 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
||||
|
||||
return result;
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException("KeywordInstance : clone() error, " + ex);
|
||||
throw new RuntimeException("KeywordInstance : clone() error", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,4 +252,26 @@ public abstract class KeywordInstance<T extends KeywordInstance<?>> implements K
|
||||
public boolean redundant(Collection<KeywordInterface> list) {
|
||||
return !list.isEmpty() && keyword.isMultipleRedundant;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.keyword.KeywordInterface#setHostCard(forge.game.card.Card)
|
||||
*/
|
||||
@Override
|
||||
public void setHostCard(Card host) {
|
||||
for (SpellAbility sa : this.abilities) {
|
||||
sa.setHostCard(host);
|
||||
}
|
||||
|
||||
for (Trigger tr : this.triggers) {
|
||||
tr.setHostCard(host);
|
||||
}
|
||||
|
||||
for (ReplacementEffect re : this.replacements) {
|
||||
re.setHostCard(host);
|
||||
}
|
||||
|
||||
for (StaticAbility sa : this.staticAbilities) {
|
||||
sa.setHostCard(host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public interface KeywordInterface extends Cloneable {
|
||||
public void addSpellAbility(final SpellAbility s);
|
||||
public void addStaticAbility(final StaticAbility st);
|
||||
|
||||
public void setHostCard(final Card host);
|
||||
|
||||
/**
|
||||
* @return the triggers
|
||||
|
||||
@@ -30,12 +30,11 @@ import forge.game.card.Card;
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: KeywordsChange.java 27095 2014-08-17 07:32:24Z elcnesh $
|
||||
*/
|
||||
public class KeywordsChange {
|
||||
private final KeywordCollection keywords = new KeywordCollection();
|
||||
private final List<KeywordInterface> removeKeywordInterfaces = Lists.newArrayList();
|
||||
private final List<String> removeKeywords = Lists.newArrayList();
|
||||
public class KeywordsChange implements Cloneable {
|
||||
private KeywordCollection keywords = new KeywordCollection();
|
||||
private List<KeywordInterface> removeKeywordInterfaces = Lists.newArrayList();
|
||||
private List<String> removeKeywords = Lists.newArrayList();
|
||||
private boolean removeAllKeywords;
|
||||
private boolean removeIntrinsicKeywords;
|
||||
|
||||
@@ -63,7 +62,7 @@ public class KeywordsChange {
|
||||
this.removeAllKeywords = removeAll;
|
||||
this.removeIntrinsicKeywords = removeIntrinsic;
|
||||
}
|
||||
|
||||
|
||||
public KeywordsChange(
|
||||
final Collection<KeywordInterface> keywordList,
|
||||
final Collection<KeywordInterface> removeKeywordInterfaces,
|
||||
@@ -172,4 +171,49 @@ public class KeywordsChange {
|
||||
removeIntrinsicKeywords = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void setHostCard(final Card host) {
|
||||
keywords.setHostCard(host);
|
||||
for (KeywordInterface k : removeKeywordInterfaces) {
|
||||
k.setHostCard(host);
|
||||
}
|
||||
}
|
||||
|
||||
public KeywordsChange copy(final Card host, final boolean lki) {
|
||||
try {
|
||||
KeywordsChange result = (KeywordsChange)super.clone();
|
||||
|
||||
result.keywords = new KeywordCollection();
|
||||
for (KeywordInterface ki : this.keywords.getValues()) {
|
||||
result.keywords.insert(ki.copy(host, lki));
|
||||
}
|
||||
|
||||
result.removeKeywords = Lists.newArrayList(removeKeywords);
|
||||
|
||||
result.removeKeywordInterfaces = Lists.newArrayList();
|
||||
for (KeywordInterface ki : this.removeKeywordInterfaces) {
|
||||
removeKeywordInterfaces.add(ki.copy(host, lki));
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException("KeywordsChange : clone() error", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<+");
|
||||
sb.append(this.keywords);
|
||||
sb.append("|-");
|
||||
sb.append(this.removeKeywordInterfaces);
|
||||
sb.append("|-");
|
||||
sb.append(this.removeKeywords);
|
||||
sb.append(">");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,7 +389,6 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
}
|
||||
|
||||
playerTurn.removeKeyword("Skip all combat phases of this turn.");
|
||||
game.getCleanup().executeUntil(getNextTurn());
|
||||
nUpkeepsThisTurn = 0;
|
||||
|
||||
// Rule 514.3
|
||||
@@ -397,6 +396,9 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
|
||||
// Rule 514.3a - state-based actions
|
||||
game.getAction().checkStateEffects(true);
|
||||
|
||||
// done this after check state effects, so it only has effect next check
|
||||
game.getCleanup().executeUntil(getNextTurn());
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -433,11 +435,7 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
|
||||
boolean manaBurns = game.getRules().hasManaBurn();
|
||||
if (manaBurns) {
|
||||
p.loseLife(burn);
|
||||
}
|
||||
// Play the Mana Burn sound
|
||||
if (burn > 0) {
|
||||
game.fireEvent(new GameEventManaBurn(burn, manaBurns));
|
||||
p.loseLife(burn,true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,7 +524,7 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
|
||||
if (canAttack) {
|
||||
if (shouldTapForAttack) {
|
||||
attacker.tap();
|
||||
attacker.tap(true);
|
||||
}
|
||||
} else {
|
||||
combat.removeFromCombat(attacker);
|
||||
@@ -722,6 +720,7 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
runParams.put("Attacker", a);
|
||||
runParams.put("Blockers", blockers);
|
||||
runParams.put("NumBlockers", blockers.size());
|
||||
runParams.put("Defender", combat.getDefenderByAttacker(a));
|
||||
runParams.put("DefendingPlayer", combat.getDefenderPlayerByAttacker(a));
|
||||
game.getTriggerHandler().runTrigger(TriggerType.AttackerBlocked, runParams, false);
|
||||
|
||||
|
||||
@@ -455,8 +455,12 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public final int loseLife(final int toLose) {
|
||||
return loseLife(toLose,false);
|
||||
}
|
||||
|
||||
public final int loseLife(final int toLose, final boolean manaBurn) {
|
||||
int lifeLost = 0;
|
||||
if (!canLoseLife()) {
|
||||
return 0;
|
||||
@@ -466,7 +470,11 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
life -= toLose;
|
||||
view.updateLife(this);
|
||||
lifeLost = toLose;
|
||||
game.fireEvent(new GameEventPlayerLivesChanged(this, oldLife, life));
|
||||
if(manaBurn) {
|
||||
game.fireEvent(new GameEventManaBurn(this,lifeLost,true));
|
||||
} else {
|
||||
game.fireEvent(new GameEventPlayerLivesChanged(this, oldLife, life));
|
||||
}
|
||||
}
|
||||
else if (toLose == 0) {
|
||||
// Rule 118.4
|
||||
@@ -515,14 +523,15 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lifePayment <= 0)
|
||||
return true;
|
||||
loseLife(lifePayment);
|
||||
|
||||
// rule 118.8
|
||||
if (life >= lifePayment) {
|
||||
return (loseLife(lifePayment) > 0);
|
||||
}
|
||||
return false;
|
||||
// Run triggers
|
||||
final Map<String, Object> runParams = Maps.newHashMap();
|
||||
runParams.put("Player", this);
|
||||
runParams.put("LifeAmount", lifePayment);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.PayLife, runParams, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public final boolean canPayEnergy(final int energyPayment) {
|
||||
@@ -1261,42 +1270,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return drawCards(1);
|
||||
}
|
||||
|
||||
public void scry(final int numScry, SpellAbility cause) {
|
||||
final CardCollection topN = new CardCollection(this.getCardsIn(ZoneType.Library, numScry));
|
||||
|
||||
if (topN.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ImmutablePair<CardCollection, CardCollection> lists = getController().arrangeForScry(topN);
|
||||
final CardCollection toTop = lists.getLeft();
|
||||
final CardCollection toBottom = lists.getRight();
|
||||
|
||||
int numToBottom = 0;
|
||||
int numToTop = 0;
|
||||
|
||||
if (toBottom != null) {
|
||||
for(Card c : toBottom) {
|
||||
getGame().getAction().moveToBottomOfLibrary(c, cause, null);
|
||||
numToBottom++;
|
||||
}
|
||||
}
|
||||
|
||||
if (toTop != null) {
|
||||
Collections.reverse(toTop); // the last card in list will become topmost in library, have to revert thus.
|
||||
for(Card c : toTop) {
|
||||
getGame().getAction().moveToLibrary(c, cause, null);
|
||||
numToTop++;
|
||||
}
|
||||
}
|
||||
|
||||
getGame().fireEvent(new GameEventScry(this, numToTop, numToBottom));
|
||||
|
||||
final Map<String, Object> runParams = Maps.newHashMap();
|
||||
runParams.put("Player", this);
|
||||
getGame().getTriggerHandler().runTrigger(TriggerType.Scry, runParams, false);
|
||||
}
|
||||
|
||||
public void surveil(int num, SpellAbility cause) {
|
||||
|
||||
final Map<String, Object> repParams = Maps.newHashMap();
|
||||
|
||||
@@ -13,8 +13,8 @@ public enum PlayerActionConfirmMode {
|
||||
ChangeZoneGeneral,
|
||||
BidLife,
|
||||
OptionalChoose,
|
||||
Tribute;
|
||||
Tribute,
|
||||
// Ripple;
|
||||
;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user