mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
Compare commits
1162 Commits
rememberSp
...
forge-1.6.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
711bc38276 | ||
|
|
eed91b4354 | ||
|
|
cbcb2037ea | ||
|
|
d3e0e8ecde | ||
|
|
3a2c7e8c8e | ||
|
|
6b3d16267b | ||
|
|
220e6eeb29 | ||
|
|
61d4faf5c0 | ||
|
|
09a9718d02 | ||
|
|
fd3deec51f | ||
|
|
2447df4dde | ||
|
|
7ae287b880 | ||
|
|
16797e67cd | ||
|
|
f1b154319f | ||
|
|
698359cbc9 | ||
|
|
16cf063427 | ||
|
|
ea3cf1a7a8 | ||
|
|
f72285e750 | ||
|
|
deae353e59 | ||
|
|
1f752ff210 | ||
|
|
bb64aa8d25 | ||
|
|
3f3b8bc41b | ||
|
|
5ae6c9da0d | ||
|
|
c609f199e4 | ||
|
|
c38f1bf214 | ||
|
|
1b47a81b8c | ||
|
|
ca87a5d857 | ||
|
|
72101790ec | ||
|
|
028afcb1be | ||
|
|
bdcf756eca | ||
|
|
3e60e547e7 | ||
|
|
202667b708 | ||
|
|
c4457110bc | ||
|
|
074f180bbd | ||
|
|
abfee4544d | ||
|
|
e19980b16a | ||
|
|
39b16fd885 | ||
|
|
29ed87af3a | ||
|
|
add3cf762a | ||
|
|
b8f7b3d4e7 | ||
|
|
8674487c4a | ||
|
|
4930bc1fd1 | ||
|
|
2e6249cb71 | ||
|
|
5eceadb33a | ||
|
|
8db8715222 | ||
|
|
538acaf45e | ||
|
|
2d38b14a4e | ||
|
|
db7d750fde | ||
|
|
c6bbc7bd2d | ||
|
|
f408950aad | ||
|
|
f7546eca32 | ||
|
|
ac66311b94 | ||
|
|
02d355fd6d | ||
|
|
7a69f89aac | ||
|
|
0196ada5bc | ||
|
|
8253dd96e7 | ||
|
|
0cdf2232b7 | ||
|
|
7269882c52 | ||
|
|
b72f965725 | ||
|
|
4ab65f6471 | ||
|
|
0b1add8098 | ||
|
|
545b085cb6 | ||
|
|
f3222e4723 | ||
|
|
e84bbaffc3 | ||
|
|
97e9e6d822 | ||
|
|
982e919c94 | ||
|
|
31be2a409e | ||
|
|
be298bf544 | ||
|
|
4383af9691 | ||
|
|
7beb8a9bf1 | ||
|
|
1bd762c0f8 | ||
|
|
8c0c78046b | ||
|
|
14c2f7aad0 | ||
|
|
62f55b9ba3 | ||
|
|
014f226d48 | ||
|
|
c544513535 | ||
|
|
b5c92bf423 | ||
|
|
730344f294 | ||
|
|
92a60a65d1 | ||
|
|
61a771fdd6 | ||
|
|
b5d1c3ec74 | ||
|
|
3b2114755c | ||
|
|
611c40af29 | ||
|
|
ba8a38c2f4 | ||
|
|
744a6a42e8 | ||
|
|
d395897ebb | ||
|
|
e6a14fa5fc | ||
|
|
3cfe595667 | ||
|
|
9979298e6d | ||
|
|
373dad5472 | ||
|
|
4c016cbbd1 | ||
|
|
1e338ec91e | ||
|
|
1352627eb9 | ||
|
|
7430ee1a69 | ||
|
|
13a950a263 | ||
|
|
7faacc29b2 | ||
|
|
1755bc2a1b | ||
|
|
25770eb84d | ||
|
|
d58a8069ce | ||
|
|
09b81ebb13 | ||
|
|
6b309fd8bf | ||
|
|
19f536097e | ||
|
|
e331701cdc | ||
|
|
6c4d81417b | ||
|
|
940ac268be | ||
|
|
211b51e516 | ||
|
|
cd30651673 | ||
|
|
9a8f6c128f | ||
|
|
e95695f3c0 | ||
|
|
ec53b55490 | ||
|
|
a248d74a97 | ||
|
|
bf947df65c | ||
|
|
b34f6e89b5 | ||
|
|
2ac5ed403b | ||
|
|
2e6e031555 | ||
|
|
bc5f011540 | ||
|
|
cbeff792e9 | ||
|
|
db5781c56a | ||
|
|
e16535e0e4 | ||
|
|
da874fe7e2 | ||
|
|
802d3d78cd | ||
|
|
70438bd6d8 | ||
|
|
890b2f0da9 | ||
|
|
9c1e36e456 | ||
|
|
cde3f824e8 | ||
|
|
adb4579d99 | ||
|
|
bcb8d0ef12 | ||
|
|
6f1cd8f080 | ||
|
|
830ecafe13 | ||
|
|
03ebbc4da3 | ||
|
|
e980af0b39 | ||
|
|
48699f4f4d | ||
|
|
95c52b7bab | ||
|
|
708c9acee1 | ||
|
|
179fcbfb0e | ||
|
|
a808888215 | ||
|
|
08e456683f | ||
|
|
3ab85f460e | ||
|
|
b984751a41 | ||
|
|
e3dfc23376 | ||
|
|
576189410e | ||
|
|
330329eb97 | ||
|
|
e367a962e8 | ||
|
|
bced8f0ea4 | ||
|
|
983425dc7d | ||
|
|
f72252dc2e | ||
|
|
f2657f1d80 | ||
|
|
a061282a54 | ||
|
|
5456bff229 | ||
|
|
1db3251fac | ||
|
|
893e900087 | ||
|
|
55900cdf6d | ||
|
|
e545be3514 | ||
|
|
bebd28af4b | ||
|
|
8a7aa10419 | ||
|
|
bda5728913 | ||
|
|
1124b695d7 | ||
|
|
e4fd36c40f | ||
|
|
495eae9c23 | ||
|
|
804b97b841 | ||
|
|
ffafa05d9b | ||
|
|
c6b59ad5f9 | ||
|
|
548cecbadb | ||
|
|
9a3278913f | ||
|
|
7164ac5b25 | ||
|
|
1fb7a9f23d | ||
|
|
0ec0c64462 | ||
|
|
1bbae111f7 | ||
|
|
efdf84ae5f | ||
|
|
bd482f17c4 | ||
|
|
c4da601195 | ||
|
|
c486415a4e | ||
|
|
c16a72b5a6 | ||
|
|
6b6b474707 | ||
|
|
810b72b1d6 | ||
|
|
03cbabbd0a | ||
|
|
c990d0eb4f | ||
|
|
3c6d82b2e8 | ||
|
|
9f4affb62c | ||
|
|
8f48f77a0b | ||
|
|
7a808c2399 | ||
|
|
9b6c056020 | ||
|
|
445c0b2c75 | ||
|
|
afbcdf3928 | ||
|
|
0ba040750e | ||
|
|
fce9d590ad | ||
|
|
be4e652b24 | ||
|
|
aed9774c7a | ||
|
|
297f315f76 | ||
|
|
147d6b789b | ||
|
|
d3ff90e154 | ||
|
|
aa57b522b7 | ||
|
|
fa1c96ce7f | ||
|
|
9b16343e8f | ||
|
|
884d51f339 | ||
|
|
c9577ec780 | ||
|
|
d75ce11ae0 | ||
|
|
4821e50025 | ||
|
|
de4e76f741 | ||
|
|
22d308812d | ||
|
|
b6f023c57c | ||
|
|
5a0b104fb3 | ||
|
|
3a9eaf52c6 | ||
|
|
9afb43d25a | ||
|
|
974ce4c724 | ||
|
|
5ccbbc559a | ||
|
|
a7d1bd7877 | ||
|
|
bc78cb7e01 | ||
|
|
cbf867106d | ||
|
|
00b5561388 | ||
|
|
01d1eacdce | ||
|
|
108a31b692 | ||
|
|
b4294d39f5 | ||
|
|
b95022740b | ||
|
|
3d2173f441 | ||
|
|
d02ad1a294 | ||
|
|
7c0b11d479 | ||
|
|
bfae7b8951 | ||
|
|
3d22299341 | ||
|
|
13583d0aba | ||
|
|
f2accc9b04 | ||
|
|
24c9caf703 | ||
|
|
dfae9158c9 | ||
|
|
4d5ffe5230 | ||
|
|
317920406c | ||
|
|
c298840489 | ||
|
|
4dd411d619 | ||
|
|
e644ab4649 | ||
|
|
5dd8619663 | ||
|
|
3c1ccfef82 | ||
|
|
8d94663f4a | ||
|
|
08d3015ed5 | ||
|
|
a85017b0b7 | ||
|
|
97e2d627f3 | ||
|
|
1a5362ab03 | ||
|
|
537fad612f | ||
|
|
c0b76016f1 | ||
|
|
ff45188dff | ||
|
|
257ba16c5b | ||
|
|
2617795e37 | ||
|
|
5c95216d9d | ||
|
|
e2c175439f | ||
|
|
94b2dfc282 | ||
|
|
d5c8aa765f | ||
|
|
9d75d13be1 | ||
|
|
7025098797 | ||
|
|
9157fa728b | ||
|
|
64f17935e5 | ||
|
|
765586b8b0 | ||
|
|
d2143e2f2a | ||
|
|
a969d9d615 | ||
|
|
2041b9da1a | ||
|
|
1d6beb1085 | ||
|
|
16a662ab30 | ||
|
|
d0c39e7b14 | ||
|
|
fdf3d3588e | ||
|
|
da4f1ade9d | ||
|
|
07e327828d | ||
|
|
2db2ba5375 | ||
|
|
b4448b9384 | ||
|
|
cc19b2b39e | ||
|
|
abea68241e | ||
|
|
64eacd07a1 | ||
|
|
e0101ebb7b | ||
|
|
cdc032b39f | ||
|
|
e48858b4ce | ||
|
|
ed7611a332 | ||
|
|
b97e6fa3e4 | ||
|
|
aa677ca306 | ||
|
|
33fe26a892 | ||
|
|
405cbd5ab3 | ||
|
|
5b39be5fe9 | ||
|
|
f6b38bf9ee | ||
|
|
9812bbb0da | ||
|
|
af7cb0ae30 | ||
|
|
38b183b795 | ||
|
|
06afb22fd7 | ||
|
|
04cd2e8c45 | ||
|
|
6cf73e2a5d | ||
|
|
28ee9eb58d | ||
|
|
ada8d59b2c | ||
|
|
6a755db00d | ||
|
|
2bd008328a | ||
|
|
347266382f | ||
|
|
d087fac128 | ||
|
|
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>
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -231,3 +231,8 @@ forge-gui/tools/oracleScript.log
|
||||
/release.properties
|
||||
/target
|
||||
/test-output
|
||||
.settings
|
||||
.classpath
|
||||
.project
|
||||
.vscode/settings.json
|
||||
.vscode/launch.json
|
||||
|
||||
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
|
||||
|
||||
Quick start guide for [setting up the Forge project within IntelliJ](https://git.cardforge.org/core-developers/forge/wikis/intellij-setup).
|
||||
|
||||
# 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.24</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
|
||||
@@ -18,12 +18,10 @@
|
||||
package forge.ai;
|
||||
|
||||
import com.esotericsoftware.minlog.Log;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import forge.ai.ability.ChangeZoneAi;
|
||||
import forge.ai.ability.ExploreAi;
|
||||
import forge.ai.simulation.SpellAbilityPicker;
|
||||
@@ -1211,7 +1209,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);
|
||||
@@ -1784,75 +1782,6 @@ public class AiController {
|
||||
throw new UnsupportedOperationException("AI is not supposed to reach this code at the moment");
|
||||
}
|
||||
|
||||
public Map<GameEntity, CounterType> chooseProliferation(final SpellAbility sa) {
|
||||
final Map<GameEntity, CounterType> result = Maps.newHashMap();
|
||||
|
||||
final List<Player> allies = player.getAllies();
|
||||
allies.add(player);
|
||||
final List<Player> enemies = player.getOpponents();
|
||||
final Function<Card, CounterType> predProliferate = new Function<Card, CounterType>() {
|
||||
@Override
|
||||
public CounterType apply(Card crd) {
|
||||
//fast way out, no need to check other stuff
|
||||
if (!crd.hasCounters()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// cards controlled by ai or ally with Vanishing or Fading
|
||||
// and exaclty one counter of the specifice type gets high priority to keep the card
|
||||
if (allies.contains(crd.getController())) {
|
||||
// except if its a Chronozoa, because it WANTS to be removed to make more
|
||||
if (crd.hasKeyword(Keyword.VANISHING) && !"Chronozoa".equals(crd.getName())) {
|
||||
if (crd.getCounters(CounterType.TIME) == 1) {
|
||||
return CounterType.TIME;
|
||||
}
|
||||
} else if (crd.hasKeyword(Keyword.FADING)) {
|
||||
if (crd.getCounters(CounterType.FADE) == 1) {
|
||||
return CounterType.FADE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final Entry<CounterType, Integer> c1 : crd.getCounters().entrySet()) {
|
||||
// if card can not recive the given counter, try another one
|
||||
if (!crd.canReceiveCounters(c1.getKey())) {
|
||||
continue;
|
||||
}
|
||||
if (ComputerUtil.isNegativeCounter(c1.getKey(), crd) && enemies.contains(crd.getController())) {
|
||||
return c1.getKey();
|
||||
}
|
||||
if (!ComputerUtil.isNegativeCounter(c1.getKey(), crd) && allies.contains(crd.getController())) {
|
||||
return c1.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
for (Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
||||
CounterType ct = predProliferate.apply(c);
|
||||
if (ct != null)
|
||||
result.put(c, ct);
|
||||
}
|
||||
|
||||
for (Player e : enemies) {
|
||||
// TODO In the future check of enemies can get poison counters and give them some other bad counter type
|
||||
if (e.getCounters(CounterType.POISON) > 0) {
|
||||
result.put(e, CounterType.POISON);
|
||||
}
|
||||
}
|
||||
|
||||
for (Player pl : allies) {
|
||||
if (pl.getCounters(CounterType.EXPERIENCE) > 0) {
|
||||
result.put(pl, CounterType.EXPERIENCE);
|
||||
} else if (pl.getCounters(CounterType.ENERGY) > 0) {
|
||||
result.put(pl, CounterType.ENERGY);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public CardCollection chooseCardsForEffect(CardCollectionView pool, SpellAbility sa, int min, int max, boolean isOptional) {
|
||||
if (sa == null || sa.getApi() == null) {
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1601,7 +1601,7 @@ public class ComputerUtilCard {
|
||||
pumped.addChangedCardKeywords(kws, null, false, false, timestamp);
|
||||
Set<CounterType> types = c.getCounters().keySet();
|
||||
for(CounterType ct : types) {
|
||||
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, true);
|
||||
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, true, null);
|
||||
}
|
||||
//Copies tap-state and extra keywords (auras, equipment, etc.)
|
||||
if (c.isTapped()) {
|
||||
|
||||
@@ -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() {
|
||||
@@ -981,7 +1016,7 @@ public abstract class GameState {
|
||||
String[] allCounterStrings = counterString.split(",");
|
||||
for (final String counterPair : allCounterStrings) {
|
||||
String[] pair = counterPair.split("=", 2);
|
||||
entity.addCounter(CounterType.valueOf(pair[0]), Integer.parseInt(pair[1]), null, false, false);
|
||||
entity.addCounter(CounterType.valueOf(pair[0]), Integer.parseInt(pair[1]), null, false, false, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1047,7 +1082,6 @@ public abstract class GameState {
|
||||
zone.setCards(kv.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1108,7 +1142,7 @@ public abstract class GameState {
|
||||
} else if (info.startsWith("SummonSick")) {
|
||||
c.setSickness(true);
|
||||
} else if (info.startsWith("FaceDown")) {
|
||||
c.setState(CardStateName.FaceDown, true);
|
||||
c.turnFaceDown(true);
|
||||
if (info.endsWith("Manifested")) {
|
||||
c.setManifested(true);
|
||||
}
|
||||
@@ -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
|
||||
@@ -341,8 +367,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
// need a custom predicate here since Volrath's Shapeshifter may have a different name OTB
|
||||
return card.getName().equals("Volrath's Shapeshifter")
|
||||
|| card.getStates().contains(CardStateName.OriginalText) && card.getState(CardStateName.OriginalText).getName().equals("Volrath's Shapeshifter");
|
||||
return card.getOriginalState(CardStateName.Original).getName().equals("Volrath's Shapeshifter");
|
||||
}
|
||||
}).isEmpty()) {
|
||||
int bestValue = 0;
|
||||
@@ -927,11 +952,6 @@ public class PlayerControllerAi extends PlayerController {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<GameEntity, CounterType> chooseProliferation(SpellAbility sa) {
|
||||
return brains.chooseProliferation(sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chooseTargetsFor(SpellAbility currentAbility) {
|
||||
return brains.doTrigger(currentAbility, true);
|
||||
@@ -1090,7 +1110,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 +1184,10 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
return chosenOptCosts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmMulliganScry(Player p) {
|
||||
// Always true?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class)
|
||||
.put(ApiType.AddPhase, AddPhaseAi.class)
|
||||
.put(ApiType.AddTurn, AddTurnAi.class)
|
||||
.put(ApiType.Amass, AmassAi.class)
|
||||
.put(ApiType.Animate, AnimateAi.class)
|
||||
.put(ApiType.AnimateAll, AnimateAllAi.class)
|
||||
.put(ApiType.Attach, AttachAi.class)
|
||||
|
||||
102
forge-ai/src/main/java/forge/ai/ability/AmassAi.java
Normal file
102
forge-ai/src/main/java/forge/ai/ability/AmassAi.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class AmassAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean checkApiLogic(Player ai, final SpellAbility sa) {
|
||||
CardCollection aiArmies = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Army"));
|
||||
Card host = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
if (!aiArmies.isEmpty()) {
|
||||
if (CardLists.count(aiArmies, CardPredicates.canReceiveCounters(CounterType.P1P1)) <= 0) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
final String tokenScript = "b_0_0_zombie_army";
|
||||
final int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Num", "1"), sa);
|
||||
|
||||
Card token = TokenInfo.getProtoType(tokenScript, sa);
|
||||
|
||||
if (token == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
token.setController(ai, 0);
|
||||
token.setLastKnownZone(ai.getZone(ZoneType.Battlefield));
|
||||
|
||||
boolean result = true;
|
||||
|
||||
// need to check what the cards would be on the battlefield
|
||||
// do not attach yet, that would cause Events
|
||||
CardCollection preList = new CardCollection(token);
|
||||
game.getAction().checkStaticAbilities(false, Sets.newHashSet(token), preList);
|
||||
|
||||
if (token.canReceiveCounters(CounterType.P1P1)) {
|
||||
token.setCounters(CounterType.P1P1, amount);
|
||||
}
|
||||
|
||||
if (token.isCreature() && token.getNetToughness() < 1) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
//reset static abilities
|
||||
game.getAction().checkStaticAbilities(false);
|
||||
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
// TODO: Special check for instant speed logic? Something like Lazotep Plating.
|
||||
/*
|
||||
boolean isInstant = sa.getRestrictions().isInstantSpeed();
|
||||
CardCollection aiArmies = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Army"));
|
||||
|
||||
if (isInstant) {
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return mandatory || checkApiLogic(ai, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
Iterable<Card> better = CardLists.filter(options, CardPredicates.canReceiveCounters(CounterType.P1P1));
|
||||
if (Iterables.isEmpty(better)) {
|
||||
better = options;
|
||||
}
|
||||
return ComputerUtilCard.getBestAI(better);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,10 +233,17 @@ 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))) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
@@ -21,7 +20,6 @@ public class CloneAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = source.getGame();
|
||||
|
||||
@@ -39,27 +37,13 @@ public class CloneAi extends SpellAbilityAi {
|
||||
// TODO - add some kind of check for during human turn to answer
|
||||
// "Can I use this to block something?"
|
||||
|
||||
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PhaseHandler phase = game.getPhaseHandler();
|
||||
// don't use instant speed clone abilities outside computers
|
||||
// Combat_Begin step
|
||||
if (!phase.is(PhaseType.COMBAT_BEGIN)
|
||||
&& phase.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't use instant speed clone abilities outside humans
|
||||
// Combat_Declare_Attackers_InstantAbility step
|
||||
if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || phase.isPlayerTurn(ai) || game.getCombat().getAttackers().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't activate during main2 unless this effect is permanent
|
||||
if (phase.is(PhaseType.MAIN2) && !sa.hasParam("Permanent")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (null == tgt) {
|
||||
if (!sa.usesTargeting()) {
|
||||
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||
|
||||
boolean bFlag = false;
|
||||
@@ -186,9 +170,13 @@ public class CloneAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||
Player targetedPlayer) {
|
||||
|
||||
final Card host = sa.getHostCard();
|
||||
final Player ctrl = host.getController();
|
||||
|
||||
final Card cloneTarget = getCloneTarget(sa);
|
||||
final boolean isOpp = cloneTarget.getController().isOpponentOf(sa.getActivatingPlayer());
|
||||
|
||||
final boolean isVesuva = "Vesuva".equals(host.getName());
|
||||
|
||||
final String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
||||
@@ -198,7 +186,8 @@ public class CloneAi extends SpellAbilityAi {
|
||||
if (!newOptions.isEmpty()) {
|
||||
options = newOptions;
|
||||
}
|
||||
Card choice = ComputerUtilCard.getBestAI(options);
|
||||
Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options);
|
||||
|
||||
if (isVesuva && "Vesuva".equals(choice.getName())) {
|
||||
choice = null;
|
||||
}
|
||||
@@ -206,4 +195,44 @@ public class CloneAi extends SpellAbilityAi {
|
||||
return choice;
|
||||
}
|
||||
|
||||
protected Card getCloneTarget(final SpellAbility sa) {
|
||||
final Card host = sa.getHostCard();
|
||||
Card tgtCard = host;
|
||||
if (sa.hasParam("CloneTarget")) {
|
||||
final List<Card> cloneTargets = AbilityUtils.getDefinedCards(host, sa.getParam("CloneTarget"), sa);
|
||||
if (!cloneTargets.isEmpty()) {
|
||||
tgtCard = cloneTargets.get(0);
|
||||
}
|
||||
} else if (sa.hasParam("Choices") && sa.usesTargeting()) {
|
||||
tgtCard = sa.getTargets().getFirstTargetedCard();
|
||||
}
|
||||
|
||||
return tgtCard;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player, forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
|
||||
*/
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
// don't use instant speed clone abilities outside computers
|
||||
// Combat_Begin step
|
||||
if (!ph.is(PhaseType.COMBAT_BEGIN)
|
||||
&& ph.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
|
||||
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't use instant speed clone abilities outside humans
|
||||
// Combat_Declare_Attackers_InstantAbility step
|
||||
if (!ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || ph.isPlayerTurn(ai) || ph.getCombat().getAttackers().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't activate during main2 unless this effect is permanent
|
||||
if (ph.is(PhaseType.MAIN2) && !sa.hasParam("Permanent")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -33,10 +38,14 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
||||
cperms.addAll(CardLists.filter(p.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
if (crd.hasCounters()) {
|
||||
if (!crd.hasCounters()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (crd.isPlaneswalker()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// iterate only over existing counters
|
||||
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||
if (e.getValue() >= 1 && !ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
|
||||
@@ -56,7 +65,11 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
||||
hperms.addAll(CardLists.filter(o.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
if (crd.hasCounters()) {
|
||||
if (!crd.hasCounters()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (crd.isPlaneswalker()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -95,4 +108,61 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
||||
return canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see forge.ai.SpellAbilityAi#chooseSingleEntity(forge.game.player.Player, forge.game.spellability.SpellAbility, java.util.Collection, boolean, forge.game.player.Player)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
|
||||
// Proliferate is always optional for all, no need to select best
|
||||
|
||||
// because countertype can't be chosen anymore, only look for posion counters
|
||||
for (final Player p : Iterables.filter(options, Player.class)) {
|
||||
if (p.isOpponentOf(ai)) {
|
||||
if (p.getCounters(CounterType.POISON) > 0 && p.canReceiveCounters(CounterType.POISON)) {
|
||||
return (T)p;
|
||||
}
|
||||
} else {
|
||||
if (p.getCounters(CounterType.POISON) <= 5 || p.canReceiveCounters(CounterType.POISON)) {
|
||||
return (T)p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final Card c : Iterables.filter(options, Card.class)) {
|
||||
// AI planeswalker always, opponent planeswalkers never
|
||||
if (c.isPlaneswalker()) {
|
||||
if (c.getController().isOpponentOf(ai)) {
|
||||
continue;
|
||||
} else {
|
||||
return (T)c;
|
||||
}
|
||||
}
|
||||
|
||||
final Card lki = CardUtil.getLKICopy(c);
|
||||
// update all the counters there
|
||||
boolean hasNegative = false;
|
||||
for (final CounterType ct : c.getCounters().keySet()) {
|
||||
hasNegative = hasNegative || ComputerUtil.isNegativeCounter(ct, c);
|
||||
lki.setCounters(ct, lki.getCounters(ct) + 1);
|
||||
}
|
||||
|
||||
// TODO need more logic there?
|
||||
// it tries to evaluate the creatures
|
||||
if (c.isCreature()) {
|
||||
if (c.getController().isOpponentOf(ai) ==
|
||||
(ComputerUtilCard.evaluateCreature(lki, true, false)
|
||||
< ComputerUtilCard.evaluateCreature(c, true, false))) {
|
||||
return (T)c;
|
||||
}
|
||||
} else {
|
||||
if (!c.getController().isOpponentOf(ai) && !hasNegative) {
|
||||
return (T)c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.*;
|
||||
@@ -354,7 +355,25 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||
// TODO Auto-generated method stub
|
||||
GameEntity target = (GameEntity) params.get("Target");
|
||||
CounterType type = (CounterType) params.get("CounterType");
|
||||
|
||||
if (target instanceof Card) {
|
||||
Card targetCard = (Card) target;
|
||||
if (targetCard.getController().isOpponentOf(player)) {
|
||||
return !ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
|
||||
} else {
|
||||
return ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
|
||||
}
|
||||
} else if (target instanceof Player) {
|
||||
Player targetPlayer = (Player) target;
|
||||
if (targetPlayer.isOpponentOf(player)) {
|
||||
return !type.equals(CounterType.POISON) ? max : min;
|
||||
} else {
|
||||
return type.equals(CounterType.POISON) ? max : min;
|
||||
}
|
||||
}
|
||||
|
||||
return super.chooseNumber(player, sa, min, max, params);
|
||||
}
|
||||
|
||||
@@ -370,30 +389,49 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
return super.chooseCounterType(options, sa, params);
|
||||
}
|
||||
Player ai = sa.getActivatingPlayer();
|
||||
Card target = (Card) params.get("Target");
|
||||
GameEntity target = (GameEntity) params.get("Target");
|
||||
|
||||
if (target.getController().isOpponentOf(ai)) {
|
||||
if (target instanceof Card) {
|
||||
Card targetCard = (Card) target;
|
||||
if (targetCard.getController().isOpponentOf(ai)) {
|
||||
// if its a Planeswalker try to remove Loyality first
|
||||
if (target.isPlaneswalker()) {
|
||||
if (targetCard.isPlaneswalker()) {
|
||||
return CounterType.LOYALTY;
|
||||
}
|
||||
for (CounterType type : options) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, target)) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, targetCard)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (options.contains(CounterType.M1M1) && target.hasKeyword(Keyword.PERSIST)) {
|
||||
return CounterType.M1M1;
|
||||
} else if (options.contains(CounterType.P1P1) && target.hasKeyword(Keyword.UNDYING)) {
|
||||
if (options.contains(CounterType.M1M1) && targetCard.hasKeyword(Keyword.PERSIST)) {
|
||||
return CounterType.M1M1;
|
||||
} else if (options.contains(CounterType.P1P1) && targetCard.hasKeyword(Keyword.UNDYING)) {
|
||||
return CounterType.P1P1;
|
||||
}
|
||||
for (CounterType type : options) {
|
||||
if (ComputerUtil.isNegativeCounter(type, target)) {
|
||||
if (ComputerUtil.isNegativeCounter(type, targetCard)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (target instanceof Player) {
|
||||
Player targetPlayer = (Player) target;
|
||||
if (targetPlayer.isOpponentOf(ai)) {
|
||||
for (CounterType type : options) {
|
||||
if (!type.equals(CounterType.POISON)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (CounterType type : options) {
|
||||
if (type.equals(CounterType.POISON)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.chooseCounterType(options, sa, params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
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();
|
||||
|
||||
@@ -150,9 +150,11 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
|
||||
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
Player opp = ComputerUtil.getOpponentFor(ai);
|
||||
for (Player opp : ai.getOpponents()) {
|
||||
if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) {
|
||||
return false;
|
||||
continue;
|
||||
} else if (!opp.canDiscardBy(sa)) { // e.g. Tamiyo, Collector of Tales
|
||||
continue;
|
||||
}
|
||||
if (tgt != null) {
|
||||
if (sa.canTarget(opp)) {
|
||||
@@ -160,6 +162,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} // discardTargetAI()
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.google.common.collect.Maps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
@@ -104,7 +103,7 @@ public class ManifestAi extends SpellAbilityAi {
|
||||
// check to ensure that there are no replacement effects that prevent creatures ETBing from library
|
||||
// (e.g. Grafdigger's Cage)
|
||||
Card topCopy = CardUtil.getLKICopy(library.getFirst());
|
||||
topCopy.setState(CardStateName.FaceDown, false);
|
||||
topCopy.turnFaceDownNoUpdate();
|
||||
topCopy.setManifested(true);
|
||||
|
||||
final Map<String, Object> repParams = Maps.newHashMap();
|
||||
@@ -113,7 +112,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)) {
|
||||
} 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");
|
||||
|
||||
@@ -19,10 +19,7 @@ import forge.game.GameObjectMap;
|
||||
import forge.game.GameRules;
|
||||
import forge.game.Match;
|
||||
import forge.game.StaticEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardFactory;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
@@ -267,6 +264,7 @@ public class GameCopier {
|
||||
System.err.println(sa.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return newCard;
|
||||
}
|
||||
|
||||
@@ -295,6 +293,8 @@ public class GameCopier {
|
||||
|
||||
newCard.setChangedCardTypes(c.getChangedCardTypesMap());
|
||||
newCard.setChangedCardKeywords(c.getChangedCardKeywords());
|
||||
newCard.setChangedCardNames(c.getChangedCardNames());
|
||||
|
||||
// TODO: Is this correct? Does it not duplicate keywords from enchantments and such?
|
||||
for (KeywordInterface kw : c.getHiddenExtrinsicKeywords())
|
||||
newCard.addHiddenExtrinsicKeyword(kw);
|
||||
@@ -305,12 +305,13 @@ public class GameCopier {
|
||||
if (c.isFaceDown()) {
|
||||
boolean isCreature = newCard.isCreature();
|
||||
boolean hasManaCost = !newCard.getManaCost().isNoCost();
|
||||
newCard.setState(CardStateName.FaceDown, true);
|
||||
newCard.turnFaceDown(true);
|
||||
if (c.isManifested()) {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,6 +335,11 @@ public class GameCopier {
|
||||
}
|
||||
}
|
||||
|
||||
newCard.setFlipped(c.isFlipped());
|
||||
for (Map.Entry<Long, CardCloneStates> e : c.getCloneStates().entrySet()) {
|
||||
newCard.addCloneState(e.getValue().copy(newCard, true), e.getKey());
|
||||
}
|
||||
|
||||
Map<CounterType, Integer> counters = c.getCounters();
|
||||
if (!counters.isEmpty()) {
|
||||
newCard.setCounters(Maps.newEnumMap(counters));
|
||||
|
||||
@@ -194,6 +194,8 @@ public class GameSimulator {
|
||||
System.out.println();
|
||||
}
|
||||
final SpellAbility playingSa = sa;
|
||||
|
||||
simGame.copyLastState();
|
||||
ComputerUtil.handlePlayingSpellAbility(aiPlayer, sa, simGame, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@@ -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.24</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)));
|
||||
|
||||
|
||||
@@ -5,13 +5,12 @@ public enum CardStateName {
|
||||
Original,
|
||||
FaceDown,
|
||||
Flipped,
|
||||
Cloner,
|
||||
Transformed,
|
||||
Meld,
|
||||
Cloned,
|
||||
LeftSplit,
|
||||
RightSplit,
|
||||
OriginalText; // backup state for cards like Volrath's Shapeshifter
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this method.
|
||||
|
||||
@@ -41,6 +41,7 @@ public final class ColorSet implements Comparable<ColorSet>, Iterable<Byte>, Ser
|
||||
private static final long serialVersionUID = 794691267379929080L;
|
||||
|
||||
private final byte myColor;
|
||||
public byte getMyColor() { return myColor; }
|
||||
private final float orderWeight;
|
||||
|
||||
private static final ColorSet[] cache = new ColorSet[32];
|
||||
|
||||
@@ -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,15 @@ 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 getImageFilename(1); }
|
||||
public String getImageFilename(int idx) { return imageFileName.get(idx-1); }
|
||||
|
||||
@Override public String getItemType() { return "Token"; }
|
||||
|
||||
@@ -139,6 +147,11 @@ 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 getImageKey(idx);
|
||||
}
|
||||
|
||||
public String getImageKey(int artIndex) {
|
||||
return ImageKeys.TOKEN_PREFIX + imageFileName.get(artIndex).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.24</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
@@ -466,7 +466,7 @@ public class Game {
|
||||
}
|
||||
|
||||
public Zone getZoneOf(final Card card) {
|
||||
return card.getZone();
|
||||
return card.getLastKnownZone();
|
||||
}
|
||||
|
||||
public synchronized CardCollectionView getCardsIn(final ZoneType zone) {
|
||||
@@ -901,12 +901,10 @@ public class Game {
|
||||
for (Card c : p.getAllCards()) {
|
||||
if (c.hasSVar("NeedsOrderedGraveyard")) {
|
||||
return true;
|
||||
} else if (c.getStates().contains(CardStateName.OriginalText)) {
|
||||
if (c.getState(CardStateName.OriginalText).hasSVar("NeedsOrderedGraveyard")) {
|
||||
} else if (c.getOriginalState(CardStateName.Original).hasSVar("NeedsOrderedGraveyard")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Card c : p.getOpponents().getCardsIn(ZoneType.Battlefield)) {
|
||||
// Bone Dancer is important when an opponent has it active on the battlefield
|
||||
if ("opponent".equalsIgnoreCase(c.getSVar("NeedsOrderedGraveyard"))) {
|
||||
|
||||
@@ -50,9 +50,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 +70,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 +102,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 +146,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,39 +183,30 @@ public class GameAction {
|
||||
lastKnownInfo = CardUtil.getLKICopy(c);
|
||||
}
|
||||
|
||||
if (!c.isToken()) {
|
||||
if (c.isCloned()) {
|
||||
c.switchStates(CardStateName.Original, CardStateName.Cloner, false);
|
||||
c.setState(CardStateName.Original, false);
|
||||
c.clearStates(CardStateName.Cloner, false);
|
||||
if (c.isFlipCard()) {
|
||||
c.clearStates(CardStateName.Flipped, false);
|
||||
}
|
||||
if (c.getStates().contains(CardStateName.OriginalText)) {
|
||||
c.clearStates(CardStateName.OriginalText, false);
|
||||
c.removeSVar("GainingTextFrom");
|
||||
c.removeSVar("GainingTextFromTimestamp");
|
||||
}
|
||||
c.updateStateForView();
|
||||
} else if (c.getStates().contains(CardStateName.OriginalText)) {
|
||||
// Volrath's Shapeshifter
|
||||
CardFactory.copyState(c, CardStateName.OriginalText, c, CardStateName.Original, false);
|
||||
c.setState(CardStateName.Original, false);
|
||||
c.clearStates(CardStateName.OriginalText, false);
|
||||
c.removeSVar("GainingTextFrom");
|
||||
c.removeSVar("GainingTextFromTimestamp");
|
||||
c.updateStateForView();
|
||||
// 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 (fromBattlefield && c.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);
|
||||
if (!c.isToken()) {
|
||||
if (c.isCloned() || c.hasTextChangeState()) {
|
||||
c.removeCloneStates();
|
||||
c.removeTextChangeStates();
|
||||
c.updateStateForView();
|
||||
}
|
||||
|
||||
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)
|
||||
copied.setState(CardStateName.Original, false);
|
||||
}
|
||||
|
||||
copied.setUnearthed(c.isUnearthed());
|
||||
copied.setTapped(false);
|
||||
|
||||
@@ -248,11 +233,10 @@ 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;
|
||||
}
|
||||
Card noLandLKI = c;
|
||||
if (!c.isLand()) {
|
||||
// check if something would be a land
|
||||
Card noLandLKI = CardUtil.getLKICopy(c);
|
||||
noLandLKI = CardUtil.getLKICopy(c);
|
||||
// this check needs to check if this card would be on the battlefield
|
||||
noLandLKI.setLastKnownZone(zoneTo);
|
||||
|
||||
@@ -262,12 +246,12 @@ public class GameAction {
|
||||
// fake etb counters thing, then if something changed,
|
||||
// need to apply checkStaticAbilities again
|
||||
if(!noLandLKI.isLand()) {
|
||||
if (noLandLKI.putEtbCounters()) {
|
||||
if (noLandLKI.putEtbCounters(null)) {
|
||||
// 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 +260,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 +268,18 @@ public class GameAction {
|
||||
|
||||
// not to battlefield anymore!
|
||||
toBattlefield = false;
|
||||
|
||||
if (c.isCloned() || c.hasTextChangeState()) {
|
||||
c.removeCloneStates();
|
||||
c.removeTextChangeStates();
|
||||
c.updateStateForView();
|
||||
}
|
||||
|
||||
if (copied.getCurrentStateName() != CardStateName.Original) {
|
||||
copied.setState(CardStateName.Original, false);
|
||||
}
|
||||
|
||||
copied.updateStateForView();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,11 +352,34 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
|
||||
// need to suspend cards own replacement effects
|
||||
if (!suppress) {
|
||||
if (toBattlefield && !copied.getEtbCounters().isEmpty()) {
|
||||
for (final ReplacementEffect re : copied.getReplacementEffects()) {
|
||||
re.setSuppressed(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "enter the battlefield as a copy" - apply code here
|
||||
// but how to query for input here and continue later while the callers assume synchronous result?
|
||||
zoneTo.add(copied, position, c); // the modified state of the card is also reported here (e.g. for Morbid + Awaken)
|
||||
c.setZone(zoneTo);
|
||||
|
||||
// do ETB counters after zone add
|
||||
if (!suppress) {
|
||||
if (toBattlefield ) {
|
||||
copied.putEtbCounters(table);
|
||||
// enable replacement effects again
|
||||
for (final ReplacementEffect re : copied.getReplacementEffects()) {
|
||||
re.setSuppressed(false);
|
||||
}
|
||||
}
|
||||
copied.clearEtbCounters();
|
||||
}
|
||||
|
||||
if (fromBattlefield) {
|
||||
c.setDamage(0); //clear damage after a card leaves the battlefield
|
||||
c.setHasBeenDealtDeathtouchDamage(false);
|
||||
@@ -373,18 +393,9 @@ 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) {
|
||||
if (toBattlefield) {
|
||||
if (copied.putEtbCounters()) {
|
||||
// if counter where put of card, call checkStaticAbilities again
|
||||
checkStaticAbilities();
|
||||
}
|
||||
}
|
||||
copied.clearEtbCounters();
|
||||
}
|
||||
table.triggerCountersPutAll(game);
|
||||
|
||||
// play the change zone sound
|
||||
game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo));
|
||||
@@ -435,10 +446,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 +481,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());
|
||||
@@ -804,6 +819,7 @@ public class GameAction {
|
||||
|
||||
final Map<StaticAbility, CardCollectionView> affectedPerAbility = Maps.newHashMap();
|
||||
for (final StaticAbilityLayer layer : StaticAbilityLayer.CONTINUOUS_LAYERS) {
|
||||
List<StaticAbility> toAdd = Lists.newArrayList();
|
||||
for (final StaticAbility stAb : staticAbilities) {
|
||||
final CardCollectionView previouslyAffected = affectedPerAbility.get(stAb);
|
||||
final CardCollectionView affectedHere;
|
||||
@@ -816,8 +832,18 @@ public class GameAction {
|
||||
affectedHere = previouslyAffected;
|
||||
stAb.applyContinuousAbility(layer, previouslyAffected);
|
||||
}
|
||||
if (affectedHere != null) {
|
||||
for (final Card c : affectedHere) {
|
||||
for (final StaticAbility st2 : c.getStaticAbilities()) {
|
||||
if (!staticAbilities.contains(st2)) {
|
||||
toAdd.add(st2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
staticAbilities.addAll(toAdd);
|
||||
}
|
||||
|
||||
for (final CardCollectionView affected : affectedPerAbility.values()) {
|
||||
if (affected != null) {
|
||||
@@ -881,6 +907,7 @@ public class GameAction {
|
||||
|
||||
// Update P/T and type in the view only once after all the cards have been processed, to avoid flickering
|
||||
for (Card c : affectedCards) {
|
||||
c.updateNameforView();
|
||||
c.updatePowerToughnessForView();
|
||||
c.updateTypesForView();
|
||||
c.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering
|
||||
@@ -1094,7 +1121,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 +1696,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,7 +1713,6 @@ 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);
|
||||
@@ -1702,20 +1724,6 @@ public class GameAction {
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
p.onMulliganned();
|
||||
allKept = false;
|
||||
} else {
|
||||
@@ -1726,21 +1734,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 +1842,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,52 +66,52 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
}
|
||||
|
||||
public final int addDamage(final int damage, final Card source, boolean isCombat, boolean noPrevention,
|
||||
final CardDamageMap damageMap, final CardDamageMap preventMap, final SpellAbility cause) {
|
||||
final CardDamageMap damageMap, final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
|
||||
if (noPrevention) {
|
||||
return addDamageWithoutPrevention(damage, source, damageMap, preventMap, cause);
|
||||
return addDamageWithoutPrevention(damage, source, damageMap, preventMap, counterTable, cause);
|
||||
} else if (isCombat) {
|
||||
return addCombatDamage(damage, source, damageMap, preventMap);
|
||||
return addCombatDamage(damage, source, damageMap, preventMap, counterTable);
|
||||
} else {
|
||||
return addDamage(damage, source, damageMap, preventMap, cause);
|
||||
return addDamage(damage, source, damageMap, preventMap, counterTable, cause);
|
||||
}
|
||||
}
|
||||
|
||||
public int addDamage(final int damage, final Card source, final CardDamageMap damageMap,
|
||||
final CardDamageMap preventMap, final SpellAbility cause) {
|
||||
final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
|
||||
int damageToDo = damage;
|
||||
|
||||
damageToDo = replaceDamage(damageToDo, source, false, true, damageMap, preventMap, cause);
|
||||
damageToDo = replaceDamage(damageToDo, source, false, true, damageMap, preventMap, counterTable, cause);
|
||||
damageToDo = preventDamage(damageToDo, source, false, preventMap, cause);
|
||||
|
||||
return addDamageAfterPrevention(damageToDo, source, false, damageMap);
|
||||
return addDamageAfterPrevention(damageToDo, source, false, damageMap, counterTable);
|
||||
}
|
||||
|
||||
public final int addCombatDamage(final int damage, final Card source, final CardDamageMap damageMap,
|
||||
final CardDamageMap preventMap) {
|
||||
final CardDamageMap preventMap, GameEntityCounterTable counterTable) {
|
||||
int damageToDo = damage;
|
||||
|
||||
damageToDo = replaceDamage(damageToDo, source, true, true, damageMap, preventMap, null);
|
||||
damageToDo = replaceDamage(damageToDo, source, true, true, damageMap, preventMap, counterTable, null);
|
||||
damageToDo = preventDamage(damageToDo, source, true, preventMap, null);
|
||||
|
||||
if (damageToDo > 0) {
|
||||
source.getDamageHistory().registerCombatDamage(this);
|
||||
}
|
||||
// damage prevention is already checked
|
||||
return addCombatDamageBase(damageToDo, source, damageMap);
|
||||
return addCombatDamageBase(damageToDo, source, damageMap, counterTable);
|
||||
}
|
||||
|
||||
protected int addCombatDamageBase(final int damage, final Card source, CardDamageMap damageMap) {
|
||||
return addDamageAfterPrevention(damage, source, true, damageMap);
|
||||
protected int addCombatDamageBase(final int damage, final Card source, CardDamageMap damageMap, GameEntityCounterTable counterTable) {
|
||||
return addDamageAfterPrevention(damage, source, true, damageMap, counterTable);
|
||||
}
|
||||
|
||||
public int addDamageWithoutPrevention(final int damage, final Card source, final CardDamageMap damageMap,
|
||||
final CardDamageMap preventMap, final SpellAbility cause) {
|
||||
int damageToDo = replaceDamage(damage, source, false, false, damageMap, preventMap, cause);
|
||||
return addDamageAfterPrevention(damageToDo, source, false, damageMap);
|
||||
final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
|
||||
int damageToDo = replaceDamage(damage, source, false, false, damageMap, preventMap, counterTable, cause);
|
||||
return addDamageAfterPrevention(damageToDo, source, false, damageMap, counterTable);
|
||||
}
|
||||
|
||||
public int replaceDamage(final int damage, final Card source, final boolean isCombat, final boolean prevention,
|
||||
final CardDamageMap damageMap, final CardDamageMap preventMap, final SpellAbility cause) {
|
||||
final CardDamageMap damageMap, final CardDamageMap preventMap, GameEntityCounterTable counterTable, final SpellAbility cause) {
|
||||
// Replacement effects
|
||||
final Map<String, Object> repParams = Maps.newHashMap();
|
||||
repParams.put("Event", "DamageDone");
|
||||
@@ -122,6 +122,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
repParams.put("NoPreventDamage", !prevention);
|
||||
repParams.put("DamageMap", damageMap);
|
||||
repParams.put("PreventMap", preventMap);
|
||||
repParams.put("CounterTable", counterTable);
|
||||
if (cause != null) {
|
||||
repParams.put("Cause", cause);
|
||||
}
|
||||
@@ -139,7 +140,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
if (prevention) {
|
||||
newDamage = newTarget.preventDamage(newDamage, source, isCombat, preventMap, cause);
|
||||
}
|
||||
newTarget.addDamageAfterPrevention(newDamage, source, isCombat, damageMap);
|
||||
newTarget.addDamageAfterPrevention(newDamage, source, isCombat, damageMap, counterTable);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
@@ -147,7 +148,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
}
|
||||
|
||||
// This function handles damage after replacement and prevention effects are applied
|
||||
public abstract int addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat, CardDamageMap damageMap);
|
||||
public abstract int addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat, CardDamageMap damageMap, GameEntityCounterTable counterTable);
|
||||
|
||||
// This should be also usable by the AI to forecast an effect (so it must
|
||||
// not change the game state)
|
||||
@@ -470,7 +471,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
abstract public void setCounters(final Map<CounterType, Integer> allCounters);
|
||||
|
||||
abstract public boolean canReceiveCounters(final CounterType type);
|
||||
abstract public int addCounter(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier, final boolean fireEvents);
|
||||
abstract public int addCounter(final CounterType counterType, final int n, final Player source, final boolean applyMultiplier, final boolean fireEvents, GameEntityCounterTable table);
|
||||
abstract public void subtractCounter(final CounterType counterName, final int n);
|
||||
abstract public void clearCounters();
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package forge.game;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.ForwardingTable;
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
|
||||
public class GameEntityCounterTable extends ForwardingTable<GameEntity, CounterType, Integer> {
|
||||
|
||||
private Table<GameEntity, CounterType, Integer> dataMap = HashBasedTable.create();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.google.common.collect.ForwardingTable#delegate()
|
||||
*/
|
||||
@Override
|
||||
protected Table<GameEntity, CounterType, Integer> delegate() {
|
||||
return dataMap;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.google.common.collect.ForwardingTable#put(java.lang.Object, java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Integer put(GameEntity rowKey, CounterType columnKey, Integer value) {
|
||||
Integer old = contains(rowKey, columnKey) ? get(rowKey, columnKey) : 0;
|
||||
return super.put(rowKey, columnKey, old + value);
|
||||
}
|
||||
|
||||
public Map<GameEntity, Integer> filterTable(CounterType type, String valid, Card host, SpellAbility sa) {
|
||||
Map<GameEntity, Integer> result = Maps.newHashMap();
|
||||
|
||||
for (Map.Entry<GameEntity, Integer> e : column(type).entrySet()) {
|
||||
if (e.getKey().isValid(valid, host.getController(), host, sa)) {
|
||||
result.put(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void triggerCountersPutAll(final Game game) {
|
||||
if (!isEmpty()) {
|
||||
final Map<String, Object> runParams = Maps.newHashMap();
|
||||
runParams.put("Objects", this);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.CounterAddedAll, runParams, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,28 +7,30 @@ import forge.deck.Deck;
|
||||
import forge.deck.DeckFormat;
|
||||
import forge.deck.DeckSection;
|
||||
import forge.game.player.RegisteredPlayer;
|
||||
import forge.util.Localizer;
|
||||
|
||||
public enum GameType {
|
||||
Sealed (DeckFormat.Limited, true, true, true, "Sealed", ""),
|
||||
Draft (DeckFormat.Limited, true, true, true, "Draft", ""),
|
||||
Winston (DeckFormat.Limited, true, true, true, "Winston", ""),
|
||||
Gauntlet (DeckFormat.Constructed, false, true, true, "Gauntlet", ""),
|
||||
Tournament (DeckFormat.Constructed, false, true, true, "Tournament", ""),
|
||||
Quest (DeckFormat.QuestDeck, true, true, false, "Quest", ""),
|
||||
QuestDraft (DeckFormat.Limited, true, true, true, "Quest Draft", ""),
|
||||
PlanarConquest (DeckFormat.PlanarConquest, true, false, false, "Planar Conquest", ""),
|
||||
Puzzle (DeckFormat.Puzzle, false, false, false, "Puzzle", "Solve a puzzle from the given game state"),
|
||||
Constructed (DeckFormat.Constructed, false, true, true, "Constructed", ""),
|
||||
DeckManager (DeckFormat.Constructed, false, true, true, "Deck Manager", ""),
|
||||
Vanguard (DeckFormat.Vanguard, true, true, true, "Vanguard", "Each player has a special \"Avatar\" card that affects the game."),
|
||||
Commander (DeckFormat.Commander, false, false, false, "Commander", "Each player has a legendary \"General\" card which can be cast at any time and determines deck colors."),
|
||||
TinyLeaders (DeckFormat.TinyLeaders, false, false, false, "Tiny Leaders", "Each player has a legendary \"General\" card which can be cast at any time and determines deck colors. Each card must have CMC less than 4."),
|
||||
Brawl (DeckFormat.Brawl, false, false, false, "Brawl", "Each player has a legendary \"General\" card which can be cast at any time and determines deck colors. Only cards legal in Standard may be used."),
|
||||
Planeswalker (DeckFormat.PlanarConquest, false, false, true, "Planeswalker", "Each player has a Planeswalker card which can be cast at any time."),
|
||||
Planechase (DeckFormat.Planechase, false, false, true, "Planechase", "Plane cards apply global effects. The Plane card changes when a player rolls \"Planeswalk\" on the planar die."),
|
||||
Archenemy (DeckFormat.Archenemy, false, false, true, "Archenemy", "One player is the Archenemy and fights the other players by playing Scheme cards."),
|
||||
ArchenemyRumble (DeckFormat.Archenemy, false, false, true, "Archenemy Rumble", "All players are Archenemies and can play Scheme cards."),
|
||||
MomirBasic (DeckFormat.Constructed, false, false, false, "Momir Basic", "Each player has a deck containing 60 basic lands and the Momir Vig avatar.", new Function<RegisteredPlayer, Deck>() {
|
||||
|
||||
Sealed (DeckFormat.Limited, true, true, true, "lblSealed", ""),
|
||||
Draft (DeckFormat.Limited, true, true, true, "lblDraft", ""),
|
||||
Winston (DeckFormat.Limited, true, true, true, "lblWinston", ""),
|
||||
Gauntlet (DeckFormat.Constructed, false, true, true, "lblGauntlet", ""),
|
||||
Tournament (DeckFormat.Constructed, false, true, true, "lblTournament", ""),
|
||||
Quest (DeckFormat.QuestDeck, true, true, false, "lblQuest", ""),
|
||||
QuestDraft (DeckFormat.Limited, true, true, true, "lblQuestDraft", ""),
|
||||
PlanarConquest (DeckFormat.PlanarConquest, true, false, false, "lblPlanarConquest", ""),
|
||||
Puzzle (DeckFormat.Puzzle, false, false, false, "lblPuzzle", "lblPuzzleDesc"),
|
||||
Constructed (DeckFormat.Constructed, false, true, true, "lblConstructed", ""),
|
||||
DeckManager (DeckFormat.Constructed, false, true, true, "lblDeckManager", ""),
|
||||
Vanguard (DeckFormat.Vanguard, true, true, true, "lblVanguard", "lblVanguardDesc"),
|
||||
Commander (DeckFormat.Commander, false, false, false, "lblCommander", "lblCommanderDesc"),
|
||||
TinyLeaders (DeckFormat.TinyLeaders, false, false, false, "lblTinyLeaders", "lblTinyLeadersDesc"),
|
||||
Brawl (DeckFormat.Brawl, false, false, false, "lblBrawl", "lblBrawlDesc"),
|
||||
Planeswalker (DeckFormat.PlanarConquest, false, false, true, "lblPlaneswalker", "lblPlaneswalkerDesc"),
|
||||
Planechase (DeckFormat.Planechase, false, false, true, "lblPlanechase", "lblPlanechaseDesc"),
|
||||
Archenemy (DeckFormat.Archenemy, false, false, true, "lblArchenemy", "lblArchenemyDesc"),
|
||||
ArchenemyRumble (DeckFormat.Archenemy, false, false, true, "lblArchenemyRumble", "lblArchenemyRumbleDesc"),
|
||||
MomirBasic (DeckFormat.Constructed, false, false, false, "lblMomirBasic", "lblMomirBasicDesc", new Function<RegisteredPlayer, Deck>() {
|
||||
@Override
|
||||
public Deck apply(RegisteredPlayer player) {
|
||||
Deck deck = new Deck();
|
||||
@@ -43,7 +45,7 @@ public enum GameType {
|
||||
return deck;
|
||||
}
|
||||
}),
|
||||
MoJhoSto (DeckFormat.Constructed, false, false, false, "MoJhoSto", "Each player has a deck containing 60 basic lands and the Momir Vig, Jhoira of the Ghitu, and Stonehewer Giant avatars.", new Function<RegisteredPlayer, Deck>() {
|
||||
MoJhoSto (DeckFormat.Constructed, false, false, false, "lblMoJhoSto", "lblMoJhoStoDesc", new Function<RegisteredPlayer, Deck>() {
|
||||
@Override
|
||||
public Deck apply(RegisteredPlayer player) {
|
||||
Deck deck = new Deck();
|
||||
@@ -69,14 +71,19 @@ public enum GameType {
|
||||
private final Function<RegisteredPlayer, Deck> deckAutoGenerator;
|
||||
|
||||
private GameType(DeckFormat deckFormat0, boolean isCardPoolLimited0, boolean canSideboard0, boolean addWonCardsMidgame0, String name0, String description0) {
|
||||
|
||||
this(deckFormat0, isCardPoolLimited0, canSideboard0, addWonCardsMidgame0, name0, description0, null);
|
||||
}
|
||||
private GameType(DeckFormat deckFormat0, boolean isCardPoolLimited0, boolean canSideboard0, boolean addWonCardsMidgame0, String name0, String description0, Function<RegisteredPlayer, Deck> deckAutoGenerator0) {
|
||||
final Localizer localizer = forge.util.Localizer.getInstance();
|
||||
deckFormat = deckFormat0;
|
||||
isCardPoolLimited = isCardPoolLimited0;
|
||||
canSideboard = canSideboard0;
|
||||
addWonCardsMidGame = addWonCardsMidgame0;
|
||||
name = name0;
|
||||
name = localizer.getMessage(name0);
|
||||
if (description0.length()>0) {
|
||||
description0 = localizer.getMessage(description0);
|
||||
}
|
||||
description = description0;
|
||||
deckAutoGenerator = deckAutoGenerator0;
|
||||
}
|
||||
|
||||
@@ -998,6 +998,10 @@ public class StaticEffect {
|
||||
affectedCard.removeWithFlash(getTimestamp());
|
||||
}
|
||||
|
||||
if (params.containsKey("GainTextOf")) {
|
||||
affectedCard.removeTextChangeState(getTimestamp());
|
||||
}
|
||||
|
||||
affectedCard.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering
|
||||
}
|
||||
return affectedCards;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
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));
|
||||
@@ -791,6 +787,9 @@ public class AbilityUtils {
|
||||
if (type.contains("Card")) {
|
||||
o = sa.getTriggeringObject("Card");
|
||||
}
|
||||
else if (type.contains("Object")) {
|
||||
o = sa.getTriggeringObject("Object");
|
||||
}
|
||||
else if (type.contains("Attacker")) {
|
||||
o = sa.getTriggeringObject("Attacker");
|
||||
}
|
||||
@@ -805,8 +804,8 @@ public class AbilityUtils {
|
||||
return new CardCollection();
|
||||
}
|
||||
|
||||
if (type.equals("Triggered") || (type.equals("TriggeredCard")) || (type.equals("TriggeredAttacker"))
|
||||
|| (type.equals("TriggeredBlocker"))) {
|
||||
if (type.equals("Triggered") || type.equals("TriggeredCard") || type.equals("TriggeredObject")
|
||||
|| type.equals("TriggeredAttacker") || type.equals("TriggeredBlocker")) {
|
||||
type = "Card.Self";
|
||||
}
|
||||
|
||||
@@ -814,6 +813,9 @@ public class AbilityUtils {
|
||||
if (type.contains("TriggeredCard")) {
|
||||
type = TextUtil.fastReplace(type, "TriggeredCard", "Card");
|
||||
}
|
||||
else if (type.contains("TriggeredObject")) {
|
||||
type = TextUtil.fastReplace(type, "TriggeredObject", "Card");
|
||||
}
|
||||
else if (type.contains("TriggeredAttacker")) {
|
||||
type = TextUtil.fastReplace(type, "TriggeredAttacker", "Card");
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ public enum ApiType {
|
||||
AddOrRemoveCounter (CountersPutOrRemoveEffect.class),
|
||||
AddPhase (AddPhaseEffect.class),
|
||||
AddTurn (AddTurnEffect.class),
|
||||
Amass (AmassEffect.class),
|
||||
Animate (AnimateEffect.class),
|
||||
AnimateAll (AnimateAllEffect.class),
|
||||
Attach (AttachEffect.class),
|
||||
|
||||
@@ -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();
|
||||
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) {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
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.CardPredicates;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.event.GameEventTokenCreated;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Lang;
|
||||
|
||||
public class AmassEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder("Amass ");
|
||||
final Card card = sa.getHostCard();
|
||||
final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa);
|
||||
|
||||
sb.append(amount).append(" (Put ");
|
||||
|
||||
sb.append(Lang.nounWithNumeral(amount, "+1/+1 counter"));
|
||||
|
||||
sb.append("on an Army you control. If you don’t control one, create a 0/0 black Zombie Army creature token first.)");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Game game = card.getGame();
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
final PlayerController pc = activator.getController();
|
||||
|
||||
final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa);
|
||||
final boolean remember = sa.hasParam("RememberAmass");
|
||||
|
||||
// create army token if needed
|
||||
if (CardLists.count(activator.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Army")) == 0) {
|
||||
final String tokenScript = "b_0_0_zombie_army";
|
||||
|
||||
final Card prototype = TokenInfo.getProtoType(tokenScript, sa);
|
||||
|
||||
for (final Card tok : TokenInfo.makeTokensFromPrototype(prototype, activator, 1, true)) {
|
||||
|
||||
// Should this be catching the Card that's returned?
|
||||
Card c = game.getAction().moveToPlay(tok, sa);
|
||||
c.updateStateForView();
|
||||
}
|
||||
|
||||
game.fireEvent(new GameEventTokenCreated());
|
||||
}
|
||||
|
||||
CardCollectionView tgtCards = CardLists.getType(activator.getCardsIn(ZoneType.Battlefield), "Army");
|
||||
tgtCards = pc.chooseCardsForEffect(tgtCards, sa, "Choose an army to put counters on", 1, 1, false);
|
||||
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
for(final Card tgtCard : tgtCards) {
|
||||
tgtCard.addCounter(CounterType.P1P1, amount, activator, true, table);
|
||||
game.updateLastStateForCard(tgtCard);
|
||||
|
||||
if (remember) {
|
||||
card.addRemembered(tgtCard);
|
||||
}
|
||||
}
|
||||
table.triggerCountersPutAll(game);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -152,6 +152,10 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
doAnimate(c, sa, power, toughness, types, removeTypes, finalDesc,
|
||||
keywords, removeKeywords, hiddenKeywords, timestamp);
|
||||
|
||||
if (sa.hasParam("Name")) {
|
||||
c.addChangedName(sa.getParam("Name"), timestamp);
|
||||
}
|
||||
|
||||
if (sa.hasParam("LeaveBattlefield")) {
|
||||
addLeaveBattlefieldReplacement(c, sa, sa.getParam("LeaveBattlefield"));
|
||||
}
|
||||
@@ -287,6 +291,8 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
addedAbilities, addedTriggers, addedReplacements,
|
||||
addedStaticAbilities, timestamp);
|
||||
|
||||
c.removeChangedName(timestamp);
|
||||
|
||||
game.fireEvent(new GameEventCardStatsChanged(c));
|
||||
|
||||
for (final SpellAbility sa : removedAbilities) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -2,7 +2,6 @@ package forge.game.ability.effects;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Maps;
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameActionUtil;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -178,7 +177,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
movedCard.setExiledWith(host);
|
||||
}
|
||||
if (sa.hasParam("ExileFaceDown")) {
|
||||
movedCard.setState(CardStateName.FaceDown, true);
|
||||
movedCard.turnFaceDown(true);
|
||||
}
|
||||
if (sa.hasParam("Tapped")) {
|
||||
movedCard.setTapped(true);
|
||||
|
||||
@@ -5,10 +5,8 @@ import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.GameCommand;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.CardType;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameObject;
|
||||
@@ -29,12 +27,12 @@ import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.Zone;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.*;
|
||||
import forge.util.Lang;
|
||||
import forge.util.MessageUtil;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollection;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -377,8 +375,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();
|
||||
@@ -401,6 +398,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
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 +410,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 +427,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)) {
|
||||
@@ -543,7 +540,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
movedCard.updateStateForView();
|
||||
}
|
||||
if (sa.hasParam("FaceDown")) {
|
||||
movedCard.setState(CardStateName.FaceDown, true);
|
||||
movedCard.turnFaceDown(true);
|
||||
}
|
||||
if (sa.hasParam("Attacking")) {
|
||||
// What should they attack?
|
||||
@@ -593,7 +590,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (sa.hasParam("ExileFaceDown")) {
|
||||
movedCard.setState(CardStateName.FaceDown, true);
|
||||
movedCard.turnFaceDown(true);
|
||||
}
|
||||
|
||||
if (sa.hasParam("TrackDiscarded")) {
|
||||
@@ -606,6 +603,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 +856,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) {
|
||||
@@ -1044,10 +1048,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
// need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger
|
||||
if (sa.hasParam("FaceDown") && ZoneType.Battlefield.equals(destination)) {
|
||||
c.setState(CardStateName.FaceDown, true);
|
||||
c.turnFaceDown(true);
|
||||
|
||||
// set New Pt doesn't work because this values need to be copyable for clone effects
|
||||
if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")) {
|
||||
if (sa.hasParam("FaceDownPower")) {
|
||||
c.setBasePower(AbilityUtils.calculateAmount(
|
||||
source, sa.getParam("FaceDownPower"), sa));
|
||||
@@ -1056,12 +1059,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
c.setBaseToughness(AbilityUtils.calculateAmount(
|
||||
source, sa.getParam("FaceDownToughness"), sa));
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("FaceDownAddType")) {
|
||||
CardType t = new CardType(c.getCurrentState().getType());
|
||||
t.addAll(Arrays.asList(sa.getParam("FaceDownAddType").split(",")));
|
||||
c.getCurrentState().setType(t);
|
||||
for (String type : sa.getParam("FaceDownAddType").split(",")) {
|
||||
c.addType(type);
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness")
|
||||
@@ -1085,7 +1087,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
|
||||
// need to do that again?
|
||||
if (sa.hasParam("FaceDown") && !ZoneType.Battlefield.equals(destination)) {
|
||||
movedCard.setState(CardStateName.FaceDown, true);
|
||||
movedCard.turnFaceDown(true);
|
||||
}
|
||||
movedCard.setTimestamp(ts);
|
||||
}
|
||||
@@ -1099,7 +1101,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
movedCard.setExiledWith(host);
|
||||
}
|
||||
if (sa.hasParam("ExileFaceDown")) {
|
||||
movedCard.setState(CardStateName.FaceDown, true);
|
||||
movedCard.turnFaceDown(true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -1121,6 +1123,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 +1178,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 +1218,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) {
|
||||
|
||||
@@ -48,8 +48,11 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
||||
// Sac a permanent in presence of Sigarda, Host of Herons
|
||||
// TODO: generalize this by testing if the unless cost can be paid
|
||||
if (unlessCost.startsWith("Sac<")) {
|
||||
if (saChoice.getActivatingPlayer().isOpponentOf(p)
|
||||
&& p.hasKeyword("Spells and abilities your opponents control can't cause you to sacrifice permanents.")) {
|
||||
if (!p.canSacrificeBy(saChoice)) {
|
||||
saToRemove.add(saChoice);
|
||||
}
|
||||
} else if (unlessCost.startsWith("Discard<")) {
|
||||
if (!p.canDiscardBy(sa)) {
|
||||
saToRemove.add(saChoice);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -60,8 +61,6 @@ public class ClashEffect extends SpellAbilityEffect {
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.GameCommand;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.*;
|
||||
import forge.game.event.GameEventCardStatsChanged;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
public class CloneEffect extends SpellAbilityEffect {
|
||||
// TODO update this method
|
||||
@@ -57,8 +50,12 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
Card tgtCard = host;
|
||||
final Map<String, String> origSVars = host.getSVars();
|
||||
final Game game = activator.getGame();
|
||||
final List<String> pumpKeywords = Lists.newArrayList();
|
||||
|
||||
if (sa.hasParam("PumpKeywords")) {
|
||||
pumpKeywords.addAll(Arrays.asList(sa.getParam("PumpKeywords").split(" & ")));
|
||||
}
|
||||
|
||||
// find cloning source i.e. thing to be copied
|
||||
Card cardToCopy = null;
|
||||
@@ -68,7 +65,17 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("ChoiceZone")) {
|
||||
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
|
||||
}
|
||||
CardCollectionView choices = game.getCardsIn(choiceZone);
|
||||
CardCollection choices = new CardCollection(game.getCardsIn(choiceZone));
|
||||
|
||||
// choices need to be filtered by LastState Battlefield or Graveyard
|
||||
// if a Clone enters the field as other cards it could clone,
|
||||
// the clone should not be able to clone them
|
||||
if (choiceZone.equals(ZoneType.Battlefield)) {
|
||||
choices.retainAll(sa.getLastStateBattlefield());
|
||||
} else if (choiceZone.equals(ZoneType.Graveyard)) {
|
||||
choices.retainAll(sa.getLastStateGraveyard());
|
||||
}
|
||||
|
||||
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, host);
|
||||
|
||||
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : "Choose a card ";
|
||||
@@ -91,11 +98,16 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
// find target of cloning i.e. card becoming a clone
|
||||
if (sa.hasParam("CloneTarget")) {
|
||||
final List<Card> cloneTargets = AbilityUtils.getDefinedCards(host, sa.getParam("CloneTarget"), sa);
|
||||
if (!cloneTargets.isEmpty()) {
|
||||
tgtCard = cloneTargets.get(0);
|
||||
game.getTriggerHandler().clearInstrinsicActiveTriggers(tgtCard, null);
|
||||
}
|
||||
} else if (sa.hasParam("Choices") && sa.usesTargeting()) {
|
||||
tgtCard = sa.getTargets().getFirstTargetedCard();
|
||||
game.getTriggerHandler().clearInstrinsicActiveTriggers(tgtCard, null);
|
||||
}
|
||||
|
||||
if (sa.hasParam("CloneZone")) {
|
||||
if (!tgtCard.getZone().is(ZoneType.smartValueOf(sa.getParam("CloneZone")))) {
|
||||
@@ -103,96 +115,16 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
// determine the image to be used for the clone
|
||||
String imageFileName = cardToCopy.getGame().getRules().canCloneUseTargetsImage() ? tgtCard.getImageKey() : cardToCopy.getImageKey();
|
||||
if (sa.hasParam("ImageSource")) { // Allow the image to be stipulated by using a defined card source
|
||||
List<Card> cloneImgSources = AbilityUtils.getDefinedCards(host, sa.getParam("ImageSource"), sa);
|
||||
if (!cloneImgSources.isEmpty()) {
|
||||
imageFileName = cloneImgSources.get(0).getImageKey();
|
||||
}
|
||||
final Long ts = game.getNextTimestamp();
|
||||
tgtCard.addCloneState(CardFactory.getCloneStates(cardToCopy, tgtCard, sa), ts);
|
||||
|
||||
// set ETB tapped of clone
|
||||
if (sa.hasParam("IntoPlayTapped")) {
|
||||
tgtCard.setTapped(true);
|
||||
}
|
||||
|
||||
final boolean keepName = sa.hasParam("KeepName");
|
||||
final String newName = sa.getParamOrDefault("NewName", null);
|
||||
final String originalName = tgtCard.getName();
|
||||
final boolean copyingSelf = (tgtCard == cardToCopy);
|
||||
final boolean isTransformed = cardToCopy.getCurrentStateName() == CardStateName.Transformed || cardToCopy.getCurrentStateName() == CardStateName.Meld || cardToCopy.getCurrentStateName() == CardStateName.Flipped;
|
||||
final CardStateName origState = isTransformed || cardToCopy.isFaceDown() ? CardStateName.Original : cardToCopy.getCurrentStateName();
|
||||
|
||||
if (!copyingSelf) {
|
||||
if (tgtCard.isCloned()) { // cloning again
|
||||
tgtCard.switchStates(CardStateName.Cloner, origState, false);
|
||||
tgtCard.setState(origState, false);
|
||||
tgtCard.clearStates(CardStateName.Cloner, false);
|
||||
}
|
||||
// add "Cloner" state to clone
|
||||
tgtCard.addAlternateState(CardStateName.Cloner, false);
|
||||
tgtCard.switchStates(origState, CardStateName.Cloner, false);
|
||||
tgtCard.setState(origState, false);
|
||||
} else {
|
||||
//copy Original state to Cloned
|
||||
tgtCard.addAlternateState(CardStateName.Cloned, false);
|
||||
tgtCard.switchStates(origState, CardStateName.Cloned, false);
|
||||
if (tgtCard.isFlipCard()) {
|
||||
tgtCard.setState(CardStateName.Original, false);
|
||||
}
|
||||
}
|
||||
|
||||
CardFactory.copyCopiableCharacteristics(cardToCopy, tgtCard);
|
||||
|
||||
// add extra abilities as granted by the copy effect
|
||||
addExtraCharacteristics(tgtCard, sa, origSVars);
|
||||
|
||||
// set the host card for copied replacement effects
|
||||
// needed for copied xPaid ETB effects (for the copy, xPaid = 0)
|
||||
for (final ReplacementEffect rep : tgtCard.getReplacementEffects()) {
|
||||
final SpellAbility newSa = rep.getOverridingAbility();
|
||||
if (newSa != null) {
|
||||
newSa.setOriginalHost(cardToCopy);
|
||||
}
|
||||
}
|
||||
|
||||
// set the host card for copied spellabilities
|
||||
for (final SpellAbility newSa : tgtCard.getSpellAbilities()) {
|
||||
newSa.setOriginalHost(cardToCopy);
|
||||
}
|
||||
|
||||
// restore name if it should be unchanged
|
||||
// this should only be used for Sakashima the Impostor Avatar
|
||||
if (keepName) {
|
||||
tgtCard.setName(originalName);
|
||||
}
|
||||
if (newName != null) {
|
||||
tgtCard.setName(newName);
|
||||
}
|
||||
|
||||
// If target is a flip card, also set characteristics of the flipped
|
||||
// state.
|
||||
if (cardToCopy.isFlipCard()) {
|
||||
final CardState flippedState = tgtCard.getState(CardStateName.Flipped);
|
||||
if (keepName) {
|
||||
flippedState.setName(originalName);
|
||||
}
|
||||
if (newName != null) {
|
||||
tgtCard.setName(newName);
|
||||
}
|
||||
//keep the Clone card image for the cloned card
|
||||
flippedState.setImageKey(imageFileName);
|
||||
}
|
||||
|
||||
//Clean up copy of cloned state
|
||||
if (copyingSelf) {
|
||||
tgtCard.clearStates(CardStateName.Cloned, false);
|
||||
}
|
||||
|
||||
//game.getTriggerHandler().registerActiveTrigger(tgtCard, false);
|
||||
|
||||
//keep the Clone card image for the cloned card
|
||||
if (cardToCopy.isFlipCard() && tgtCard.getCurrentStateName() != CardStateName.Flipped) {
|
||||
//for a flip card that isn't flipped, load the original image
|
||||
tgtCard.setImageKey(cardToCopy.getImageKey(CardStateName.Original));
|
||||
} else {
|
||||
tgtCard.setImageKey(imageFileName);
|
||||
if (!pumpKeywords.isEmpty()) {
|
||||
tgtCard.addChangedCardKeywords(pumpKeywords, Lists.<String>newArrayList(), false, false, ts);
|
||||
}
|
||||
|
||||
tgtCard.updateStateForView();
|
||||
@@ -202,7 +134,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);
|
||||
}
|
||||
|
||||
@@ -213,10 +145,7 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (cloneCard.isCloned()) {
|
||||
cloneCard.setState(CardStateName.Cloner, false);
|
||||
cloneCard.switchStates(CardStateName.Cloner, origState, false);
|
||||
cloneCard.clearStates(CardStateName.Cloner, false);
|
||||
if (cloneCard.removeCloneState(ts)) {
|
||||
cloneCard.updateStateForView();
|
||||
game.fireEvent(new GameEventCardStatsChanged(cloneCard));
|
||||
}
|
||||
@@ -233,148 +162,11 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
else if (duration.equals("UntilUnattached")) {
|
||||
sa.getHostCard().addUnattachCommand(unclone);
|
||||
}
|
||||
else if (duration.equals("UntilFacedown")) {
|
||||
sa.getHostCard().addFacedownCommand(unclone);
|
||||
}
|
||||
}
|
||||
game.fireEvent(new GameEventCardStatsChanged(tgtCard));
|
||||
} // cloneResolve
|
||||
|
||||
private static void addExtraCharacteristics(final Card tgtCard, final SpellAbility sa, final Map<String, String> origSVars) {
|
||||
// additional types to clone
|
||||
if (sa.hasParam("AddTypes")) {
|
||||
for (final String type : Arrays.asList(sa.getParam("AddTypes").split(","))) {
|
||||
tgtCard.addType(type);
|
||||
}
|
||||
}
|
||||
|
||||
// triggers to add to clone
|
||||
if (sa.hasParam("AddTriggers")) {
|
||||
for (final String s : Arrays.asList(sa.getParam("AddTriggers").split(","))) {
|
||||
if (origSVars.containsKey(s)) {
|
||||
final String actualTrigger = origSVars.get(s);
|
||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, tgtCard, true);
|
||||
tgtCard.addTrigger(parsedTrigger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SVars to add to clone
|
||||
if (sa.hasParam("AddSVars")) {
|
||||
for (final String s : Arrays.asList(sa.getParam("AddSVars").split(","))) {
|
||||
if (origSVars.containsKey(s)) {
|
||||
final String actualsVar = origSVars.get(s);
|
||||
tgtCard.setSVar(s, actualsVar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// abilities to add to clone
|
||||
if (sa.hasParam("AddAbilities")) {
|
||||
for (final String s : Arrays.asList(sa.getParam("AddAbilities").split(","))) {
|
||||
if (origSVars.containsKey(s)) {
|
||||
final String actualAbility = origSVars.get(s);
|
||||
final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, tgtCard);
|
||||
tgtCard.addSpellAbility(grantedAbility);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// keywords to add to clone
|
||||
|
||||
if (sa.hasParam("AddKeywords")) {
|
||||
final List<String> keywords = Arrays.asList(sa.getParam("AddKeywords").split(" & "));
|
||||
// allow SVar substitution for keywords
|
||||
for (int i = 0; i < keywords.size(); i++) {
|
||||
String k = keywords.get(i);
|
||||
if (origSVars.containsKey(k)) {
|
||||
keywords.add("\"" + k + "\"");
|
||||
keywords.remove(k);
|
||||
}
|
||||
k = keywords.get(i);
|
||||
|
||||
tgtCard.addIntrinsicKeyword(k);
|
||||
}
|
||||
}
|
||||
|
||||
// set ETB tapped of clone
|
||||
if (sa.hasParam("IntoPlayTapped")) {
|
||||
tgtCard.setTapped(true);
|
||||
}
|
||||
|
||||
// set power of clone
|
||||
if (sa.hasParam("SetPower")) {
|
||||
String rhs = sa.getParam("SetPower");
|
||||
int power = Integer.MAX_VALUE;
|
||||
try {
|
||||
power = Integer.parseInt(rhs);
|
||||
} catch (final NumberFormatException e) {
|
||||
power = CardFactoryUtil.xCount(tgtCard, tgtCard.getSVar(rhs));
|
||||
}
|
||||
for (StaticAbility sta : tgtCard.getStaticAbilities()) {
|
||||
Map<String, String> params = sta.getMapParams();
|
||||
if (params.containsKey("CharacteristicDefining") && params.containsKey("SetPower"))
|
||||
tgtCard.removeStaticAbility(sta);
|
||||
}
|
||||
tgtCard.setBasePower(power);
|
||||
}
|
||||
|
||||
// set toughness of clone
|
||||
if (sa.hasParam("SetToughness")) {
|
||||
String rhs = sa.getParam("SetToughness");
|
||||
int toughness = Integer.MAX_VALUE;
|
||||
try {
|
||||
toughness = Integer.parseInt(rhs);
|
||||
} catch (final NumberFormatException e) {
|
||||
toughness = CardFactoryUtil.xCount(tgtCard, tgtCard.getSVar(rhs));
|
||||
}
|
||||
for (StaticAbility sta : tgtCard.getStaticAbilities()) {
|
||||
Map<String, String> params = sta.getMapParams();
|
||||
if (params.containsKey("CharacteristicDefining") && params.containsKey("SetToughness"))
|
||||
tgtCard.removeStaticAbility(sta);
|
||||
}
|
||||
tgtCard.setBaseToughness(toughness);
|
||||
}
|
||||
|
||||
// colors to be added or changed to
|
||||
String shortColors = "";
|
||||
if (sa.hasParam("Colors")) {
|
||||
final String colors = sa.getParam("Colors");
|
||||
if (colors.equals("ChosenColor")) {
|
||||
shortColors = CardUtil.getShortColorsString(tgtCard.getChosenColors());
|
||||
} else {
|
||||
shortColors = CardUtil.getShortColorsString(Arrays.asList(colors.split(",")));
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("OverwriteColors")) {
|
||||
tgtCard.setColor(shortColors);
|
||||
} else {
|
||||
// TODO: this actually doesn't work for some reason (and fiddling with timestamps doesn't seem to fix it).
|
||||
// No cards currently use this, but if some ever do, this code will require tweaking.
|
||||
tgtCard.addColor(shortColors, true, tgtCard.getTimestamp());
|
||||
}
|
||||
|
||||
if (sa.hasParam("Embalm") && tgtCard.isEmbalmed()) {
|
||||
tgtCard.addType("Zombie");
|
||||
tgtCard.setColor(MagicColor.WHITE);
|
||||
tgtCard.setManaCost(ManaCost.NO_COST);
|
||||
}
|
||||
if (sa.hasParam("Eternalize") && tgtCard.isEternalized()) {
|
||||
tgtCard.addType("Zombie");
|
||||
tgtCard.setColor(MagicColor.BLACK);
|
||||
tgtCard.setManaCost(ManaCost.NO_COST);
|
||||
tgtCard.setBasePower(4);
|
||||
tgtCard.setBaseToughness(4);
|
||||
}
|
||||
|
||||
if (sa.hasParam("GainThisAbility")) {
|
||||
SpellAbility root = sa.getRootAbility();
|
||||
|
||||
if (root.isTrigger() && root.getTrigger() != null) {
|
||||
tgtCard.addTrigger(root.getTrigger().copy(tgtCard, false));
|
||||
} else if (root.isReplacementAbility()) {
|
||||
tgtCard.addReplacementEffect(root.getReplacementEffect().copy(tgtCard, false));
|
||||
} else {
|
||||
tgtCard.addSpellAbility(root.copy(tgtCard, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -256,6 +256,6 @@ public class ControlGainEffect extends SpellAbilityEffect {
|
||||
if (sa.hasParam("AllValid")) {
|
||||
return AbilityUtils.filterListByType(game.getCardsIn(ZoneType.Battlefield), sa.getParam("AllValid"), sa);
|
||||
}
|
||||
return getTargetCards(sa);
|
||||
return getDefinedCardsOrTargeted(sa);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,11 @@ package forge.game.ability.effects;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ImageKeys;
|
||||
import forge.StaticData;
|
||||
import forge.card.CardRulesPredicates;
|
||||
import forge.card.CardType;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -19,16 +14,12 @@ import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactory;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.event.GameEventCombatChanged;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.item.PaperCard;
|
||||
import forge.util.Aggregates;
|
||||
@@ -164,7 +155,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 +174,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);
|
||||
}
|
||||
@@ -261,162 +250,29 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
|
||||
|
||||
private Card getProtoType(final SpellAbility sa, final Card original) {
|
||||
final Card host = sa.getHostCard();
|
||||
final List<String> keywords = Lists.newArrayList();
|
||||
final List<String> types = Lists.newArrayList();
|
||||
final List<String> svars = Lists.newArrayList();
|
||||
final List<String> triggers = Lists.newArrayList();
|
||||
boolean asNonLegendary = false;
|
||||
boolean resetActivations = false;
|
||||
final Player newOwner = sa.getActivatingPlayer();
|
||||
int id = newOwner == null ? 0 : newOwner.getGame().nextCardId();
|
||||
final Card copy = new Card(id, original.getPaperCard(), host.getGame());
|
||||
copy.setOwner(newOwner);
|
||||
copy.setSetCode(original.getSetCode());
|
||||
|
||||
if (sa.hasParam("Keywords")) {
|
||||
keywords.addAll(Arrays.asList(sa.getParam("Keywords").split(" & ")));
|
||||
}
|
||||
if (sa.hasParam("AddTypes")) {
|
||||
types.addAll(Arrays.asList(sa.getParam("AddTypes").split(" & ")));
|
||||
}
|
||||
if (sa.hasParam("NonLegendary")) {
|
||||
asNonLegendary = true;
|
||||
}
|
||||
if (sa.hasParam("ResetAbilityActivations")) {
|
||||
resetActivations = true;
|
||||
}
|
||||
if (sa.hasParam("AddSVars")) {
|
||||
svars.addAll(Arrays.asList(sa.getParam("AddSVars").split(" & ")));
|
||||
}
|
||||
if (sa.hasParam("Triggers")) {
|
||||
triggers.addAll(Arrays.asList(sa.getParam("Triggers").split(" & ")));
|
||||
if (sa.hasParam("Embalm")) {
|
||||
copy.setEmbalmed(true);
|
||||
}
|
||||
|
||||
final Card copy = CardFactory.copyCopiableCharacteristics(original, sa.getActivatingPlayer());
|
||||
if (sa.hasParam("Eternalize")) {
|
||||
copy.setEternalized(true);
|
||||
}
|
||||
|
||||
copy.setStates(CardFactory.getCloneStates(original, copy, sa));
|
||||
// force update the now set State
|
||||
copy.setState(copy.getCurrentStateName(), true, true);
|
||||
copy.setToken(true);
|
||||
copy.setCopiedPermanent(original);
|
||||
// add keywords from sa
|
||||
for (final String kw : keywords) {
|
||||
copy.addIntrinsicKeyword(kw);
|
||||
}
|
||||
if (asNonLegendary) {
|
||||
copy.removeType(CardType.Supertype.Legendary);
|
||||
}
|
||||
if (sa.hasParam("SetCreatureTypes")) {
|
||||
copy.setCreatureTypes(ImmutableList.copyOf(sa.getParam("SetCreatureTypes").split(" ")));
|
||||
}
|
||||
|
||||
if (sa.hasParam("SetColor")) {
|
||||
copy.setColor(MagicColor.fromName(sa.getParam("SetColor")));
|
||||
}
|
||||
|
||||
for (final String type : types) {
|
||||
copy.addType(type);
|
||||
}
|
||||
for (final String svar : svars) {
|
||||
String actualsVar = host.getSVar(svar);
|
||||
String name = svar;
|
||||
if (actualsVar.startsWith("SVar:")) {
|
||||
actualsVar = actualsVar.split("SVar:")[1];
|
||||
name = actualsVar.split(":")[0];
|
||||
actualsVar = actualsVar.split(":")[1];
|
||||
}
|
||||
copy.setSVar(name, actualsVar);
|
||||
}
|
||||
for (final String s : triggers) {
|
||||
final String actualTrigger = host.getSVar(s);
|
||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, copy, true);
|
||||
copy.addTrigger(parsedTrigger);
|
||||
}
|
||||
|
||||
// set power of clone
|
||||
if (sa.hasParam("SetPower")) {
|
||||
String rhs = sa.getParam("SetPower");
|
||||
int power = Integer.MAX_VALUE;
|
||||
try {
|
||||
power = Integer.parseInt(rhs);
|
||||
} catch (final NumberFormatException e) {
|
||||
power = CardFactoryUtil.xCount(copy, copy.getSVar(rhs));
|
||||
}
|
||||
copy.setBasePower(power);
|
||||
}
|
||||
|
||||
// set toughness of clone
|
||||
if (sa.hasParam("SetToughness")) {
|
||||
String rhs = sa.getParam("SetToughness");
|
||||
int toughness = Integer.MAX_VALUE;
|
||||
try {
|
||||
toughness = Integer.parseInt(rhs);
|
||||
} catch (final NumberFormatException e) {
|
||||
toughness = CardFactoryUtil.xCount(copy, copy.getSVar(rhs));
|
||||
}
|
||||
copy.setBaseToughness(toughness);
|
||||
}
|
||||
|
||||
if (sa.hasParam("AtEOTTrig")) {
|
||||
addSelfTrigger(sa, sa.getParam("AtEOTTrig"), copy);
|
||||
}
|
||||
|
||||
if (sa.hasParam("Embalm")) {
|
||||
copy.addType("Zombie");
|
||||
copy.setColor(MagicColor.WHITE);
|
||||
copy.setManaCost(ManaCost.NO_COST);
|
||||
copy.setEmbalmed(true);
|
||||
|
||||
String name = TextUtil.fastReplace(
|
||||
TextUtil.fastReplace(copy.getName(), ",", ""),
|
||||
" ", "_").toLowerCase();
|
||||
copy.setImageKey(ImageKeys.getTokenKey("embalm_" + name));
|
||||
}
|
||||
|
||||
if (sa.hasParam("Eternalize")) {
|
||||
copy.addType("Zombie");
|
||||
copy.setColor(MagicColor.BLACK);
|
||||
copy.setManaCost(ManaCost.NO_COST);
|
||||
copy.setBasePower(4);
|
||||
copy.setBaseToughness(4);
|
||||
copy.setEternalized(true);
|
||||
|
||||
String name = TextUtil.fastReplace(
|
||||
TextUtil.fastReplace(copy.getName(), ",", ""),
|
||||
" ", "_").toLowerCase();
|
||||
copy.setImageKey(ImageKeys.getTokenKey("eternalize_" + name));
|
||||
}
|
||||
|
||||
// remove some characteristic static abilties
|
||||
for (StaticAbility sta : copy.getStaticAbilities()) {
|
||||
if (!sta.hasParam("CharacteristicDefining")) {
|
||||
continue;
|
||||
}
|
||||
if (sa.hasParam("SetPower") || sa.hasParam("Eternalize")) {
|
||||
if (sta.hasParam("SetPower"))
|
||||
copy.removeStaticAbility(sta);
|
||||
}
|
||||
if (sa.hasParam("SetToughness") || sa.hasParam("Eternalize")) {
|
||||
if (sta.hasParam("SetToughness"))
|
||||
copy.removeStaticAbility(sta);
|
||||
}
|
||||
if (sa.hasParam("SetCreatureTypes")) {
|
||||
// currently only Changeling and similar should be affected by that
|
||||
// other cards using AddType$ ChosenType should not
|
||||
if (sta.hasParam("AddType") && "AllCreatureTypes".equals(sta.getParam("AddType"))) {
|
||||
copy.removeStaticAbility(sta);
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("SetColor") || sa.hasParam("Embalm") || sa.hasParam("Eternalize")) {
|
||||
if (sta.hasParam("SetColor")) {
|
||||
copy.removeStaticAbility(sta);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sa.hasParam("SetCreatureTypes")) {
|
||||
copy.removeIntrinsicKeyword("Changeling");
|
||||
}
|
||||
if (sa.hasParam("SetColor") || sa.hasParam("Embalm") || sa.hasParam("Eternalize")) {
|
||||
copy.removeIntrinsicKeyword("Devoid");
|
||||
}
|
||||
|
||||
if (resetActivations) {
|
||||
for (SpellAbility ab : copy.getSpellAbilities()) {
|
||||
ab.getRestrictions().resetTurnActivations();
|
||||
}
|
||||
}
|
||||
copy.updateStateForView();
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
@@ -78,6 +79,8 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
|
||||
// uses for multi sources -> one defined/target
|
||||
// this needs given counter type
|
||||
if (sa.hasParam("ValidSource")) {
|
||||
@@ -146,8 +149,9 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (csum > 0) {
|
||||
dest.addCounter(cType, csum, player, true);
|
||||
dest.addCounter(cType, csum, player, true, table);
|
||||
game.updateLastStateForCard(dest);
|
||||
table.triggerCountersPutAll(game);
|
||||
}
|
||||
return;
|
||||
} else if (sa.hasParam("ValidDefined")) {
|
||||
@@ -202,7 +206,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
|
||||
if (cnum > 0) {
|
||||
source.subtractCounter(cType, cnum);
|
||||
cur.addCounter(cType, cnum, player, true);
|
||||
cur.addCounter(cType, cnum, player, true, table);
|
||||
game.updateLastStateForCard(cur);
|
||||
updateSource = true;
|
||||
}
|
||||
@@ -210,6 +214,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
if (updateSource) {
|
||||
// update source
|
||||
game.updateLastStateForCard(source);
|
||||
table.triggerCountersPutAll(game);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -263,7 +268,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
|
||||
if (source.getCounters(cType) >= cntToMove) {
|
||||
source.subtractCounter(cType, cntToMove);
|
||||
cur.addCounter(cType, cntToMove, player, true);
|
||||
cur.addCounter(cType, cntToMove, player, true, table);
|
||||
game.updateLastStateForCard(cur);
|
||||
}
|
||||
} else {
|
||||
@@ -297,7 +302,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
sa, sb.toString(), 0, Math.min(tgtCounters.get(chosenType), cntToMove), params);
|
||||
|
||||
if (chosenAmount > 0) {
|
||||
dest.addCounter(chosenType, chosenAmount, player, true);
|
||||
dest.addCounter(chosenType, chosenAmount, player, true, table);
|
||||
source.subtractCounter(chosenType, chosenAmount);
|
||||
game.updateLastStateForCard(dest);
|
||||
cntToMove -= chosenAmount;
|
||||
@@ -307,5 +312,6 @@ public class CountersMoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
// update source
|
||||
game.updateLastStateForCard(source);
|
||||
table.triggerCountersPutAll(game);
|
||||
} // moveCounterResolve
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.game.ability.effects;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
@@ -43,6 +44,7 @@ public class CountersMultiplyEffect extends SpellAbilityEffect {
|
||||
final CounterType counterType = getCounterType(sa);
|
||||
final int n = Integer.valueOf(sa.getParamOrDefault("Multiplier", "2")) - 1;
|
||||
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
for (final Card tgtCard : getTargetCards(sa)) {
|
||||
Card gameCard = game.getCardState(tgtCard, null);
|
||||
// gameCard is LKI in that case, the card is not in game anymore
|
||||
@@ -52,14 +54,15 @@ public class CountersMultiplyEffect extends SpellAbilityEffect {
|
||||
continue;
|
||||
}
|
||||
if (counterType != null) {
|
||||
gameCard.addCounter(counterType, gameCard.getCounters(counterType) * n, player, true);
|
||||
gameCard.addCounter(counterType, gameCard.getCounters(counterType) * n, player, true, table);
|
||||
} else {
|
||||
for (Map.Entry<CounterType, Integer> e : gameCard.getCounters().entrySet()) {
|
||||
gameCard.addCounter(e.getKey(), e.getValue() * n, player, true);
|
||||
gameCard.addCounter(e.getKey(), e.getValue() * n, player, true, table);
|
||||
}
|
||||
}
|
||||
game.updateLastStateForCard(gameCard);
|
||||
}
|
||||
table.triggerCountersPutAll(game);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ package forge.game.ability.effects;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -20,16 +21,19 @@ public class CountersNoteEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
Card source = sa.getHostCard();
|
||||
CardCollection tgtCards = new CardCollection();
|
||||
tgtCards.addAll(getDefinedCardsOrTargeted(sa));
|
||||
final Game game = source.getGame();
|
||||
Player p = sa.getActivatingPlayer();
|
||||
String mode = sa.getParamOrDefault("Mode", "Load");
|
||||
for (Card c : tgtCards) {
|
||||
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
for (Card c : getDefinedCardsOrTargeted(sa)) {
|
||||
if (mode.equals(MODE_STORE)) {
|
||||
noteCounters(c, source);
|
||||
} else if (mode.equals(MODE_LOAD)) {
|
||||
loadCounters(c, source, sa.getActivatingPlayer());
|
||||
loadCounters(c, source, p, table);
|
||||
}
|
||||
}
|
||||
table.triggerCountersPutAll(game);
|
||||
}
|
||||
|
||||
private void noteCounters(Card notee, Card source) {
|
||||
@@ -40,11 +44,13 @@ public class CountersNoteEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadCounters(Card notee, Card source, final Player p) {
|
||||
private void loadCounters(Card notee, Card source, final Player p, GameEntityCounterTable table) {
|
||||
for(Entry<String, String> svar : source.getSVars().entrySet()) {
|
||||
String key = svar.getKey();
|
||||
if (key.startsWith(NOTE_COUNTERS)) {
|
||||
notee.addCounter(CounterType.getType(key.substring(NOTE_COUNTERS.length())), Integer.parseInt(svar.getValue()), p, false);
|
||||
notee.addCounter(
|
||||
CounterType.getType(key.substring(NOTE_COUNTERS.length())),
|
||||
Integer.parseInt(svar.getValue()), p, false, table);
|
||||
}
|
||||
// TODO Probably should "remove" the svars that were temporarily used
|
||||
}
|
||||
|
||||
@@ -2,22 +2,28 @@ package forge.game.ability.effects;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.List;
|
||||
|
||||
public class CountersProliferateEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Proliferate.");
|
||||
sb.append(" (You choose any number of permanents and/or players with ");
|
||||
sb.append("counters on them, then give each another counter of a kind already there.)");
|
||||
sb.append(" (Choose any number of permanents and/or players,");
|
||||
sb.append(" then give each another counter of each kind already there.)");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -27,18 +33,27 @@ public class CountersProliferateEffect extends SpellAbilityEffect {
|
||||
final Player p = sa.getActivatingPlayer();
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = host.getGame();
|
||||
Player controller = host.getController();
|
||||
Map<GameEntity, CounterType> proliferateChoice = controller.getController().chooseProliferation(sa);
|
||||
if (proliferateChoice == null )
|
||||
return;
|
||||
for(Entry<GameEntity, CounterType> ge: proliferateChoice.entrySet()) {
|
||||
if( ge.getKey() instanceof Player )
|
||||
((Player) ge.getKey()).addCounter(ge.getValue(), 1, p, true);
|
||||
else if( ge.getKey() instanceof Card) {
|
||||
Card c = (Card) ge.getKey();
|
||||
c.addCounter(ge.getValue(), 1, p, true);
|
||||
|
||||
PlayerController pc = p.getController();
|
||||
|
||||
FCollection<GameEntity> list = new FCollection<>();
|
||||
|
||||
list.addAll(game.getPlayers().filter(PlayerPredicates.hasCounters()));
|
||||
list.addAll(CardLists.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.hasCounters()));
|
||||
|
||||
List<GameEntity> result = pc.chooseEntitiesForEffect(list, 0, list.size(), null, sa,
|
||||
"Choose any number of permanents and/or players for proliferate", p);
|
||||
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
for (final GameEntity ge : result) {
|
||||
for (final CounterType ct : ge.getCounters().keySet()) {
|
||||
ge.addCounter(ct, 1, p, true, true, table);
|
||||
}
|
||||
if (ge instanceof Card) {
|
||||
Card c = (Card) ge;
|
||||
game.updateLastStateForCard(c);
|
||||
}
|
||||
}
|
||||
table.triggerCountersPutAll(game);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
@@ -63,15 +64,13 @@ public class CountersPutAllEffect extends SpellAbilityEffect {
|
||||
placer = AbilityUtils.getDefinedPlayers(host, pstr, sa).get(0);
|
||||
}
|
||||
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
for (final Card tgtCard : cards) {
|
||||
if (game.getZoneOf(tgtCard).is(ZoneType.Battlefield)) {
|
||||
tgtCard.addCounter(CounterType.valueOf(type), counterAmount, placer, true);
|
||||
} else {
|
||||
// adding counters to something like re-suspend cards
|
||||
tgtCard.addCounter(CounterType.valueOf(type), counterAmount, placer, false);
|
||||
}
|
||||
boolean inBattlefield = game.getZoneOf(tgtCard).is(ZoneType.Battlefield);
|
||||
tgtCard.addCounter(CounterType.valueOf(type), counterAmount, placer, inBattlefield, table);
|
||||
game.updateLastStateForCard(tgtCard);
|
||||
}
|
||||
table.triggerCountersPutAll(game);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.google.common.collect.Sets;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
@@ -133,6 +134,8 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
tgtObjects.addAll(getDefinedOrTargeted(sa, "Defined"));
|
||||
}
|
||||
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
|
||||
for (final GameObject obj : tgtObjects) {
|
||||
// check if the object is still in game or if it was moved
|
||||
Card gameCard = null;
|
||||
@@ -162,10 +165,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
if (eachExistingCounter) {
|
||||
for(CounterType ct : choices) {
|
||||
if (obj instanceof Player) {
|
||||
((Player) obj).addCounter(ct, counterAmount, placer, true);
|
||||
((Player) obj).addCounter(ct, counterAmount, placer, true, table);
|
||||
}
|
||||
if (obj instanceof Card) {
|
||||
gameCard.addCounter(ct, counterAmount, placer, true);
|
||||
gameCard.addCounter(ct, counterAmount, placer, true, table);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
@@ -199,6 +202,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);
|
||||
@@ -241,7 +252,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
if (etbcounter) {
|
||||
tgtCard.addEtbCounter(counterType, counterAmount, placer);
|
||||
} else {
|
||||
tgtCard.addCounter(counterType, counterAmount, placer, true);
|
||||
tgtCard.addCounter(counterType, counterAmount, placer, true, table);
|
||||
}
|
||||
if (remember) {
|
||||
final int value = tgtCard.getTotalCountersToAdd();
|
||||
@@ -266,13 +277,20 @@ 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
|
||||
if (etbcounter) {
|
||||
tgtCard.addEtbCounter(counterType, counterAmount, placer);
|
||||
} else {
|
||||
tgtCard.addCounter(counterType, counterAmount, placer, false);
|
||||
tgtCard.addCounter(counterType, counterAmount, placer, false, table);
|
||||
}
|
||||
}
|
||||
game.updateLastStateForCard(tgtCard);
|
||||
@@ -280,9 +298,10 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
||||
} else if (obj instanceof Player) {
|
||||
// Add Counters to players!
|
||||
Player pl = (Player) obj;
|
||||
pl.addCounter(counterType, counterAmount, placer, true);
|
||||
pl.addCounter(counterType, counterAmount, placer, true, table);
|
||||
}
|
||||
}
|
||||
table.triggerCountersPutAll(game);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
@@ -57,6 +58,8 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
|
||||
ctype = CounterType.valueOf(sa.getParam("CounterType"));
|
||||
}
|
||||
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
|
||||
for (final Card tgtCard : getDefinedCardsOrTargeted(sa)) {
|
||||
Card gameCard = game.getCardState(tgtCard, null);
|
||||
// gameCard is LKI in that case, the card is not in game anymore
|
||||
@@ -69,19 +72,20 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
|
||||
if (gameCard.hasCounters()) {
|
||||
if (sa.hasParam("EachExistingCounter")) {
|
||||
for (CounterType listType : Lists.newArrayList(gameCard.getCounters().keySet())) {
|
||||
addOrRemoveCounter(sa, gameCard, listType, counterAmount);
|
||||
addOrRemoveCounter(sa, gameCard, listType, counterAmount, table);
|
||||
}
|
||||
} else {
|
||||
addOrRemoveCounter(sa, gameCard, ctype, counterAmount);
|
||||
addOrRemoveCounter(sa, gameCard, ctype, counterAmount, table);
|
||||
}
|
||||
game.updateLastStateForCard(gameCard);
|
||||
}
|
||||
}
|
||||
}
|
||||
table.triggerCountersPutAll(game);
|
||||
}
|
||||
|
||||
private void addOrRemoveCounter(final SpellAbility sa, final Card tgtCard, CounterType ctype,
|
||||
final int counterAmount) {
|
||||
final int counterAmount, GameEntityCounterTable table) {
|
||||
final Player pl = sa.getActivatingPlayer();
|
||||
final PlayerController pc = pl.getController();
|
||||
|
||||
@@ -106,7 +110,7 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect {
|
||||
|
||||
boolean apply = zone == null || zone.is(ZoneType.Battlefield) || zone.is(ZoneType.Stack);
|
||||
|
||||
tgtCard.addCounter(chosenType, counterAmount, pl, apply);
|
||||
tgtCard.addCounter(chosenType, counterAmount, pl, apply, table);
|
||||
} else {
|
||||
tgtCard.subtractCounter(chosenType, counterAmount);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
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 +72,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,18 +102,45 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
boolean rememberRemoved = sa.hasParam("RememberRemoved");
|
||||
boolean rememberAmount = sa.hasParam("RememberAmount");
|
||||
|
||||
for (final Player tgtPlayer : getTargetPlayers(sa)) {
|
||||
// Removing energy
|
||||
if (!sa.usesTargeting() || tgtPlayer.canBeTargetedBy(sa)) {
|
||||
if (type.equals("All")) {
|
||||
for (Map.Entry<CounterType, Integer> e : tgtPlayer.getCounters().entrySet()) {
|
||||
tgtPlayer.subtractCounter(e.getKey(), e.getValue());
|
||||
}
|
||||
} else {
|
||||
if (num.equals("All")) {
|
||||
cntToRemove = tgtPlayer.getCounters(counterType);
|
||||
}
|
||||
if (type.equals("Any")) {
|
||||
removeAnyType(tgtPlayer, cntToRemove, sa);
|
||||
} else {
|
||||
tgtPlayer.subtractCounter(counterType, cntToRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,46 +156,19 @@ 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();
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Target", gameCard);
|
||||
|
||||
String prompt = "Select type of counters to remove";
|
||||
CounterType chosenType = pc.chooseCounterType(
|
||||
ImmutableList.copyOf(tgtCounters.keySet()), sa, prompt, params);
|
||||
prompt = "Select the number of " + chosenType.getName() + " counters to remove";
|
||||
int max = Math.min(cntToRemove, tgtCounters.get(chosenType));
|
||||
params = Maps.newHashMap();
|
||||
params.put("Target", gameCard);
|
||||
params.put("CounterType", chosenType);
|
||||
int chosenAmount = pc.chooseNumber(sa, prompt, 1, max, params);
|
||||
|
||||
if (chosenAmount > 0) {
|
||||
gameCard.subtractCounter(chosenType, chosenAmount);
|
||||
game.updateLastStateForCard(gameCard);
|
||||
if (rememberRemoved) {
|
||||
for (int i = 0; i < chosenAmount; i++) {
|
||||
card.addRemembered(Pair.of(chosenType, i));
|
||||
}
|
||||
}
|
||||
cntToRemove -= chosenAmount;
|
||||
}
|
||||
}
|
||||
removeAnyType(gameCard, cntToRemove, sa);
|
||||
} else {
|
||||
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 +185,60 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
game.updateLastStateForCard(gameCard);
|
||||
}
|
||||
|
||||
totalRemoved += cntToRemove;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (totalRemoved > 0 && rememberAmount) {
|
||||
// TODO use SpellAbility Remember later
|
||||
card.addRemembered(Integer.valueOf(totalRemoved));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void removeAnyType(GameEntity entity, int cntToRemove, SpellAbility sa) {
|
||||
boolean rememberRemoved = sa.hasParam("RememberRemoved");
|
||||
|
||||
final Card card = sa.getHostCard();
|
||||
final Game game = card.getGame();
|
||||
final Player player = sa.getActivatingPlayer();
|
||||
|
||||
PlayerController pc = player.getController();
|
||||
|
||||
while (cntToRemove > 0 && entity.hasCounters()) {
|
||||
final Map<CounterType, Integer> tgtCounters = entity.getCounters();
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Target", entity);
|
||||
|
||||
String prompt = "Select type of counters to remove";
|
||||
CounterType chosenType = pc.chooseCounterType(
|
||||
ImmutableList.copyOf(tgtCounters.keySet()), sa, prompt, params);
|
||||
prompt = "Select the number of " + chosenType.getName() + " counters to remove";
|
||||
int max = Math.min(cntToRemove, tgtCounters.get(chosenType));
|
||||
params = Maps.newHashMap();
|
||||
params.put("Target", entity);
|
||||
params.put("CounterType", chosenType);
|
||||
int chosenAmount = pc.chooseNumber(sa, prompt, sa.hasParam("UpTo") ? 0 : 1, max, params);
|
||||
|
||||
if (chosenAmount > 0) {
|
||||
entity.subtractCounter(chosenType, chosenAmount);
|
||||
if (entity instanceof Card) {
|
||||
Card gameCard = (Card) entity;
|
||||
game.updateLastStateForCard(gameCard);
|
||||
}
|
||||
|
||||
if (rememberRemoved) {
|
||||
for (int i = 0; i < chosenAmount; i++) {
|
||||
card.addRemembered(Pair.of(chosenType, i));
|
||||
}
|
||||
}
|
||||
cntToRemove -= chosenAmount;
|
||||
} else if (sa.hasParam("UpTo")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.game.ability.effects;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
@@ -88,6 +89,7 @@ public class DamageAllEffect extends DamageBaseEffect {
|
||||
boolean usedDamageMap = true;
|
||||
CardDamageMap damageMap = sa.getDamageMap();
|
||||
CardDamageMap preventMap = sa.getPreventMap();
|
||||
GameEntityCounterTable counterTable = new GameEntityCounterTable();
|
||||
|
||||
if (damageMap == null) {
|
||||
// make a new damage map
|
||||
@@ -97,13 +99,13 @@ public class DamageAllEffect extends DamageBaseEffect {
|
||||
}
|
||||
|
||||
for (final Card c : list) {
|
||||
c.addDamage(dmg, sourceLKI, damageMap, preventMap, sa);
|
||||
c.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa);
|
||||
}
|
||||
|
||||
if (!players.equals("")) {
|
||||
final List<Player> playerList = AbilityUtils.getDefinedPlayers(card, players, sa);
|
||||
for (final Player p : playerList) {
|
||||
p.addDamage(dmg, sourceLKI, damageMap, preventMap, sa);
|
||||
p.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,8 +123,13 @@ public class DamageAllEffect extends DamageBaseEffect {
|
||||
if (!usedDamageMap) {
|
||||
preventMap.triggerPreventDamage(false);
|
||||
damageMap.triggerDamageDoneOnce(false, sa);
|
||||
|
||||
preventMap.clear();
|
||||
damageMap.clear();
|
||||
}
|
||||
|
||||
counterTable.triggerCountersPutAll(game);
|
||||
|
||||
replaceDying(sa);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.game.ability.effects;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
@@ -119,6 +120,7 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
boolean usedDamageMap = true;
|
||||
CardDamageMap damageMap = sa.getDamageMap();
|
||||
CardDamageMap preventMap = sa.getPreventMap();
|
||||
GameEntityCounterTable counterTable = new GameEntityCounterTable();
|
||||
|
||||
if (damageMap == null) {
|
||||
// make a new damage map
|
||||
@@ -159,14 +161,19 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
Player assigningPlayer = players.get(0);
|
||||
Map<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(sourceLKI, assigneeCards, dmg, null, true);
|
||||
for (Entry<Card, Integer> dt : map.entrySet()) {
|
||||
dt.getKey().addDamage(dt.getValue(), sourceLKI, damageMap, preventMap, sa);
|
||||
dt.getKey().addDamage(dt.getValue(), sourceLKI, damageMap, preventMap, counterTable, sa);
|
||||
}
|
||||
|
||||
if (!usedDamageMap) {
|
||||
preventMap.triggerPreventDamage(false);
|
||||
// non combat damage cause lifegain there
|
||||
damageMap.triggerDamageDoneOnce(false, sa);
|
||||
|
||||
preventMap.clear();
|
||||
damageMap.clear();
|
||||
}
|
||||
|
||||
counterTable.triggerCountersPutAll(game);
|
||||
replaceDying(sa);
|
||||
return;
|
||||
}
|
||||
@@ -191,13 +198,13 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
c.clearAssignedDamage();
|
||||
}
|
||||
else {
|
||||
c.addDamage(dmg, sourceLKI, false, noPrevention, damageMap, preventMap, sa);
|
||||
c.addDamage(dmg, sourceLKI, false, noPrevention, damageMap, preventMap, counterTable, sa);
|
||||
}
|
||||
}
|
||||
} else if (o instanceof Player) {
|
||||
final Player p = (Player) o;
|
||||
if (!targeted || p.canBeTargetedBy(sa)) {
|
||||
p.addDamage(dmg, sourceLKI, false, noPrevention, damageMap, preventMap, sa);
|
||||
p.addDamage(dmg, sourceLKI, false, noPrevention, damageMap, preventMap, counterTable, sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,7 +217,11 @@ public class DamageDealEffect extends DamageBaseEffect {
|
||||
preventMap.triggerPreventDamage(false);
|
||||
// non combat damage cause lifegain there
|
||||
damageMap.triggerDamageDoneOnce(false, sa);
|
||||
|
||||
preventMap.clear();
|
||||
damageMap.clear();
|
||||
}
|
||||
counterTable.triggerCountersPutAll(game);
|
||||
replaceDying(sa);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
@@ -60,8 +62,9 @@ public class DamageEachEffect extends DamageBaseEffect {
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Game game = card.getGame();
|
||||
|
||||
FCollectionView<Card> sources = card.getGame().getCardsIn(ZoneType.Battlefield);
|
||||
FCollectionView<Card> sources = game.getCardsIn(ZoneType.Battlefield);
|
||||
if (sa.hasParam("ValidCards")) {
|
||||
sources = CardLists.getValidCards(sources, sa.getParam("ValidCards"), card.getController(), card);
|
||||
}
|
||||
@@ -73,6 +76,7 @@ public class DamageEachEffect extends DamageBaseEffect {
|
||||
boolean usedDamageMap = true;
|
||||
CardDamageMap damageMap = sa.getDamageMap();
|
||||
CardDamageMap preventMap = sa.getPreventMap();
|
||||
GameEntityCounterTable counterTable = new GameEntityCounterTable();
|
||||
|
||||
if (damageMap == null) {
|
||||
// make a new damage map
|
||||
@@ -83,21 +87,22 @@ public class DamageEachEffect extends DamageBaseEffect {
|
||||
|
||||
for (final Object o : tgts) {
|
||||
for (final Card source : sources) {
|
||||
final Card sourceLKI = source.getGame().getChangeZoneLKIInfo(source);
|
||||
final Card sourceLKI = game.getChangeZoneLKIInfo(source);
|
||||
|
||||
// TODO shouldn't that be using Num or something first?
|
||||
final int dmg = CardFactoryUtil.xCount(source, sa.getSVar("X"));
|
||||
|
||||
// System.out.println(source+" deals "+dmg+" damage to "+o.toString());
|
||||
if (o instanceof Card) {
|
||||
final Card c = (Card) o;
|
||||
if (c.isInPlay() && (!targeted || c.canBeTargetedBy(sa))) {
|
||||
c.addDamage(dmg, sourceLKI, damageMap, preventMap, sa);
|
||||
c.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa);
|
||||
}
|
||||
|
||||
} else if (o instanceof Player) {
|
||||
final Player p = (Player) o;
|
||||
if (!targeted || p.canBeTargetedBy(sa)) {
|
||||
p.addDamage(dmg, sourceLKI, damageMap, preventMap, sa);
|
||||
p.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,11 +111,11 @@ public class DamageEachEffect extends DamageBaseEffect {
|
||||
if (sa.hasParam("DefinedCards")) {
|
||||
if (sa.getParam("DefinedCards").equals("Self")) {
|
||||
for (final Card source : sources) {
|
||||
final Card sourceLKI = source.getGame().getChangeZoneLKIInfo(source);
|
||||
final Card sourceLKI = game.getChangeZoneLKIInfo(source);
|
||||
|
||||
final int dmg = CardFactoryUtil.xCount(source, card.getSVar("X"));
|
||||
// System.out.println(source+" deals "+dmg+" damage to "+source);
|
||||
source.addDamage(dmg, sourceLKI, damageMap, preventMap, sa);
|
||||
source.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa);
|
||||
}
|
||||
}
|
||||
if (sa.getParam("DefinedCards").equals("Remembered")) {
|
||||
@@ -122,7 +127,7 @@ public class DamageEachEffect extends DamageBaseEffect {
|
||||
if (o instanceof Card) {
|
||||
Card rememberedcard = (Card) o;
|
||||
// System.out.println(source + " deals " + dmg + " damage to " + rememberedcard);
|
||||
rememberedcard.addDamage(dmg, sourceLKI, damageMap, preventMap, sa);
|
||||
rememberedcard.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,8 +137,13 @@ public class DamageEachEffect extends DamageBaseEffect {
|
||||
if (!usedDamageMap) {
|
||||
preventMap.triggerPreventDamage(false);
|
||||
damageMap.triggerDamageDoneOnce(false, sa);
|
||||
|
||||
preventMap.clear();
|
||||
damageMap.clear();
|
||||
}
|
||||
|
||||
counterTable.triggerCountersPutAll(game);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameActionUtil;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
@@ -111,6 +111,7 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
CardZoneTable table = new CardZoneTable();
|
||||
GameEntityCounterTable counterTable = new GameEntityCounterTable();
|
||||
for (final Player p : tgtPlayers) {
|
||||
if (tgt != null && !p.canBeTargetedBy(sa)) {
|
||||
continue;
|
||||
@@ -187,14 +188,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 +239,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);
|
||||
}
|
||||
else {
|
||||
if (i == 0) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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")) {
|
||||
@@ -330,7 +319,7 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (sa.hasParam("ExileFaceDown")) {
|
||||
c.setState(CardStateName.FaceDown, true);
|
||||
c.turnFaceDown(true);
|
||||
}
|
||||
if (sa.hasParam("Imprint")) {
|
||||
host.addImprintedCard(c);
|
||||
@@ -350,8 +339,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 {
|
||||
@@ -392,7 +380,8 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
}
|
||||
} else if (destZone2 == ZoneType.Exile) {
|
||||
if (sa.hasParam("ExileWithCounter")) {
|
||||
c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")), 1, player, true);
|
||||
c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")),
|
||||
1, player, true, counterTable);
|
||||
}
|
||||
c.setExiledWith(effectHost);
|
||||
}
|
||||
@@ -403,6 +392,7 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
}
|
||||
//table trigger there
|
||||
table.triggerChangesZoneAll(game);
|
||||
counterTable.triggerCountersPutAll(game);
|
||||
}
|
||||
|
||||
// TODO This should be somewhere else, maybe like CardUtil or something like that
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.game.Game;
|
||||
import forge.game.GameActionUtil;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
@@ -7,6 +8,7 @@ import forge.game.card.*;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
@@ -29,9 +31,9 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
final String mode = sa.getParam("Mode");
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
final List<Player> tgtPlayers = getTargetPlayers(sa);
|
||||
final Iterable<Player> tgtPlayers = Iterables.filter(getTargetPlayers(sa), PlayerPredicates.canDiscardBy(sa));
|
||||
|
||||
if (!tgtPlayers.isEmpty()) {
|
||||
if (!Iterables.isEmpty(tgtPlayers)) {
|
||||
sb.append(Lang.joinHomogenous(tgtPlayers)).append(" ");
|
||||
|
||||
if (mode.equals("RevealYouChoose")) {
|
||||
@@ -103,6 +105,7 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final String mode = sa.getParam("Mode");
|
||||
final Game game = source.getGame();
|
||||
//final boolean anyNumber = sa.hasParam("AnyNumber");
|
||||
|
||||
final List<Card> discarded = new ArrayList<Card>();
|
||||
@@ -124,23 +127,26 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
final CardZoneTable table = new CardZoneTable();
|
||||
for (final Player p : discarders) {
|
||||
if ((mode.equals("RevealTgtChoose") && firstTarget != null) || !sa.usesTargeting() || p.canBeTargetedBy(sa)) {
|
||||
if (sa.hasParam("RememberDiscarder")) {
|
||||
if (sa.hasParam("RememberDiscarder") && p.canDiscardBy(sa)) {
|
||||
source.addRemembered(p);
|
||||
}
|
||||
final int numCardsInHand = p.getCardsIn(ZoneType.Hand).size();
|
||||
if (mode.equals("Defined")) {
|
||||
if (!p.canDiscardBy(sa)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean runDiscard = !sa.hasParam("Optional")
|
||||
|| p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, sa.getParam("DiscardMessage"));
|
||||
if (runDiscard) {
|
||||
CardCollectionView toDiscard = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedCards"), sa);
|
||||
|
||||
if (toDiscard.size() > 1) {
|
||||
toDiscard = GameActionUtil.orderCardsByTheirOwners(p.getGame(), toDiscard, ZoneType.Graveyard);
|
||||
toDiscard = GameActionUtil.orderCardsByTheirOwners(game, toDiscard, ZoneType.Graveyard);
|
||||
}
|
||||
|
||||
for (final Card c : toDiscard) {
|
||||
boolean hasDiscarded = p.discard(c, sa, table) != null;
|
||||
if (hasDiscarded) {
|
||||
if (p.discard(c, sa, table) != null) {
|
||||
discarded.add(c);
|
||||
}
|
||||
}
|
||||
@@ -155,11 +161,14 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (mode.equals("Hand")) {
|
||||
if (!p.canDiscardBy(sa)) {
|
||||
continue;
|
||||
}
|
||||
boolean shouldRemember = sa.hasParam("RememberDiscarded");
|
||||
CardCollectionView toDiscard = new CardCollection(Lists.newArrayList(p.getCardsIn(ZoneType.Hand)));
|
||||
|
||||
if (toDiscard.size() > 1) {
|
||||
toDiscard = GameActionUtil.orderCardsByTheirOwners(p.getGame(), toDiscard, ZoneType.Graveyard);
|
||||
toDiscard = GameActionUtil.orderCardsByTheirOwners(game, toDiscard, ZoneType.Graveyard);
|
||||
}
|
||||
|
||||
for(Card c : toDiscard) { // without copying will get concurrent modification exception
|
||||
@@ -171,27 +180,31 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (mode.equals("NotRemembered")) {
|
||||
if (!p.canDiscardBy(sa)) {
|
||||
continue;
|
||||
}
|
||||
CardCollectionView dPHand = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), "Card.IsNotRemembered", p, source);
|
||||
if (dPHand.size() > 1) {
|
||||
dPHand = GameActionUtil.orderCardsByTheirOwners(p.getGame(), dPHand, ZoneType.Graveyard);
|
||||
dPHand = GameActionUtil.orderCardsByTheirOwners(game, dPHand, ZoneType.Graveyard);
|
||||
}
|
||||
|
||||
for (final Card c : dPHand) {
|
||||
p.discard(c, sa, table);
|
||||
if (p.discard(c, sa, table) != null) {
|
||||
discarded.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int numCards = 1;
|
||||
if (sa.hasParam("NumCards")) {
|
||||
numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
|
||||
if (!p.getCardsIn(ZoneType.Hand).isEmpty() && p.getCardsIn(ZoneType.Hand).size() < numCards) {
|
||||
// System.out.println("Scale down discard from " + numCards + " to " + p.getCardsIn(ZoneType.Hand).size());
|
||||
numCards = p.getCardsIn(ZoneType.Hand).size();
|
||||
}
|
||||
numCards = Math.min(numCards, numCardsInHand);
|
||||
}
|
||||
|
||||
if (mode.equals("Random")) {
|
||||
if (!p.canDiscardBy(sa)) {
|
||||
continue;
|
||||
}
|
||||
String message = "Would you like to discard " + numCards + " random card(s)?";
|
||||
boolean runDiscard = !sa.hasParam("Optional") || p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, message);
|
||||
|
||||
@@ -211,7 +224,7 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
|
||||
CardCollectionView toDiscardView = toDiscard;
|
||||
if (toDiscard.size() > 1) {
|
||||
toDiscardView = GameActionUtil.orderCardsByTheirOwners(p.getGame(), toDiscard, ZoneType.Graveyard);
|
||||
toDiscardView = GameActionUtil.orderCardsByTheirOwners(game, toDiscard, ZoneType.Graveyard);
|
||||
}
|
||||
|
||||
for (Card c : toDiscardView) {
|
||||
@@ -222,13 +235,16 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
else if (mode.equals("TgtChoose") && sa.hasParam("UnlessType")) {
|
||||
if (!p.canDiscardBy(sa)) {
|
||||
continue;
|
||||
}
|
||||
if( numCardsInHand > 0 ) {
|
||||
CardCollectionView hand = p.getCardsIn(ZoneType.Hand);
|
||||
hand = CardLists.filter(hand, Presets.NON_TOKEN);
|
||||
CardCollectionView toDiscard = p.getController().chooseCardsToDiscardUnlessType(Math.min(numCards, numCardsInHand), hand, sa.getParam("UnlessType"), sa);
|
||||
|
||||
if (toDiscard.size() > 1) {
|
||||
toDiscard = GameActionUtil.orderCardsByTheirOwners(p.getGame(), toDiscard, ZoneType.Graveyard);
|
||||
toDiscard = GameActionUtil.orderCardsByTheirOwners(game, toDiscard, ZoneType.Graveyard);
|
||||
}
|
||||
|
||||
for (Card c : toDiscard) {
|
||||
@@ -240,10 +256,14 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
// Reveal
|
||||
final CardCollectionView dPHand = p.getCardsIn(ZoneType.Hand);
|
||||
|
||||
for (final Player opp : p.getOpponents()) {
|
||||
for (final Player opp : p.getAllOtherPlayers()) {
|
||||
opp.getController().reveal(dPHand, ZoneType.Hand, p, "Reveal ");
|
||||
}
|
||||
|
||||
if (!p.canDiscardBy(sa)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String valid = sa.hasParam("DiscardValid") ? sa.getParam("DiscardValid") : "Card";
|
||||
|
||||
if (valid.contains("X")) {
|
||||
@@ -254,14 +274,15 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
CardCollectionView dPChHand = CardLists.getValidCards(dPHand, valid.split(","), source.getController(), source, sa);
|
||||
dPChHand = CardLists.filter(dPChHand, Presets.NON_TOKEN);
|
||||
if (dPChHand.size() > 1) {
|
||||
dPChHand = GameActionUtil.orderCardsByTheirOwners(p.getGame(), dPChHand, ZoneType.Graveyard);
|
||||
dPChHand = GameActionUtil.orderCardsByTheirOwners(game, dPChHand, ZoneType.Graveyard);
|
||||
}
|
||||
|
||||
// Reveal cards that will be discarded?
|
||||
for (final Card c : dPChHand) {
|
||||
p.discard(c, sa, table);
|
||||
if (p.discard(c, sa, table) != null) {
|
||||
discarded.add(c);
|
||||
}
|
||||
}
|
||||
} else if (mode.equals("RevealYouChoose") || mode.equals("RevealTgtChoose") || mode.equals("TgtChoose")) {
|
||||
CardCollectionView dPHand = p.getCardsIn(ZoneType.Hand);
|
||||
dPHand = CardLists.filter(dPHand, Presets.NON_TOKEN);
|
||||
@@ -273,6 +294,7 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
int amount = StringUtils.isNumeric(amountString) ? Integer.parseInt(amountString) : CardFactoryUtil.xCount(source, source.getSVar(amountString));
|
||||
dPHand = p.getController().chooseCardsToRevealFromHand(amount, amount, dPHand);
|
||||
}
|
||||
|
||||
final String valid = sa.hasParam("DiscardValid") ? sa.getParam("DiscardValid") : "Card";
|
||||
String[] dValid = valid.split(",");
|
||||
CardCollection validCards = CardLists.getValidCards(dPHand, dValid, source.getController(), source, sa);
|
||||
@@ -284,8 +306,13 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
chooser = firstTarget;
|
||||
}
|
||||
|
||||
if (mode.startsWith("Reveal") && p != chooser)
|
||||
chooser.getGame().getAction().reveal(dPHand, p);
|
||||
if (mode.startsWith("Reveal") && p != chooser) {
|
||||
game.getAction().reveal(dPHand, p);
|
||||
}
|
||||
|
||||
if (!p.canDiscardBy(sa)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int min = sa.hasParam("AnyNumber") || sa.hasParam("Optional") ? 0 : Math.min(validCards.size(), numCards);
|
||||
int max = sa.hasParam("AnyNumber") ? validCards.size() : Math.min(validCards.size(), numCards);
|
||||
@@ -294,7 +321,7 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
|
||||
if (toBeDiscarded != null) {
|
||||
if (toBeDiscarded.size() > 1) {
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(p.getGame(), toBeDiscarded, ZoneType.Graveyard);
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard);
|
||||
}
|
||||
|
||||
if (mode.startsWith("Reveal") ) {
|
||||
@@ -303,13 +330,14 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
}
|
||||
for (Card card : toBeDiscarded) {
|
||||
if (card == null) { continue; }
|
||||
p.discard(card, sa, table);
|
||||
if (p.discard(card, sa, table) != null) {
|
||||
discarded.add(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("RememberDiscarded")) {
|
||||
for (final Card c : discarded) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.game.ability.effects;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntityCounterTable;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
@@ -47,9 +48,9 @@ public class ExploreEffect extends SpellAbilityEffect {
|
||||
final Player pl = sa.getActivatingPlayer();
|
||||
final PlayerController pc = pl.getController();
|
||||
final Game game = pl.getGame();
|
||||
List<Card> tgt = getTargetCards(sa);
|
||||
|
||||
for (final Card c : tgt) {
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
for (final Card c : getTargetCards(sa)) {
|
||||
// revealed land card
|
||||
boolean revealedLand = false;
|
||||
CardCollection top = pl.getTopXCardsFromLibrary(1);
|
||||
@@ -76,8 +77,8 @@ public class ExploreEffect extends SpellAbilityEffect {
|
||||
Card gamec = game.getCardState(c);
|
||||
// if the card is not more in the game anymore
|
||||
// this might still return true but its no problem
|
||||
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.getTimestamp() == c.getTimestamp()) {
|
||||
c.addCounter(CounterType.P1P1, 1, pl, true);
|
||||
if (game.getZoneOf(gamec).is(ZoneType.Battlefield) && gamec.equalsWithTimestamp(c)) {
|
||||
c.addCounter(CounterType.P1P1, 1, pl, true, table);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +87,7 @@ public class ExploreEffect extends SpellAbilityEffect {
|
||||
runParams.put("Card", c);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Explores, runParams, false);
|
||||
}
|
||||
table.triggerCountersPutAll(game);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user