mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 11:18:01 +00:00
Compare commits
1193 Commits
exiledWith
...
forge-1.6.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
636020d870 | ||
|
|
3150aa2e7c | ||
|
|
ce22449f41 | ||
|
|
1c938b5d29 | ||
|
|
0493104beb | ||
|
|
9786d9f927 | ||
|
|
73e5254e2b | ||
|
|
7c934d29ac | ||
|
|
39729cb3da | ||
|
|
aadfdac4c5 | ||
|
|
2e358116c2 | ||
|
|
71d88523b2 | ||
|
|
65e702aed7 | ||
|
|
99bb45d96a | ||
|
|
4f41be641a | ||
|
|
5ab030242b | ||
|
|
736d9bbd98 | ||
|
|
6295ebdb09 | ||
|
|
380a27b83d | ||
|
|
691af037a4 | ||
|
|
70cb78a67d | ||
|
|
4bd5fe0178 | ||
|
|
babd80edd9 | ||
|
|
74e4efba52 | ||
|
|
2d7e88a8c2 | ||
|
|
d82c8d466a | ||
|
|
9edbbf399f | ||
|
|
345316b9b0 | ||
|
|
0bb6636613 | ||
|
|
1929b75e34 | ||
|
|
245827825e | ||
|
|
fc9f81c5c9 | ||
|
|
987dce2643 | ||
|
|
4f2abad39d | ||
|
|
354000082b | ||
|
|
b8e87604c2 | ||
|
|
49f7ad1281 | ||
|
|
6731cdcc9d | ||
|
|
5306510ec1 | ||
|
|
18d0bf1c7b | ||
|
|
0b9755405e | ||
|
|
8b7a183e63 | ||
|
|
7a094478f3 | ||
|
|
bd063c71f8 | ||
|
|
693ef6c4d3 | ||
|
|
60a02cd2a5 | ||
|
|
e25c7dac4a | ||
|
|
d918cade67 | ||
|
|
982400b74d | ||
|
|
c00a384591 | ||
|
|
b0e1630fc5 | ||
|
|
50ffd7b4a9 | ||
|
|
e4e75a5f73 | ||
|
|
7da4381d38 | ||
|
|
ef8076b626 | ||
|
|
ab5f5c1ce9 | ||
|
|
6d442b12a0 | ||
|
|
0019f83e33 | ||
|
|
f3011b1790 | ||
|
|
ea79f84b71 | ||
|
|
040d6929dc | ||
|
|
2fe9c38fff | ||
|
|
256b54b6b5 | ||
|
|
062ad5d92f | ||
|
|
5806ef248f | ||
|
|
257fcf1b3d | ||
|
|
284db016a0 | ||
|
|
5e2898f0ae | ||
|
|
207408ae20 | ||
|
|
2843ca9b49 | ||
|
|
efc09d8a3f | ||
|
|
e6714bf45e | ||
|
|
8049e2c322 | ||
|
|
fdb0db2d41 | ||
|
|
7465031d44 | ||
|
|
15eb3a421d | ||
|
|
ec52a07fe9 | ||
|
|
b7a475cc0a | ||
|
|
aed022d099 | ||
|
|
86b37e7df0 | ||
|
|
09e9b46efd | ||
|
|
90b685c15d | ||
|
|
daedffc0da | ||
|
|
c489f88f77 | ||
|
|
33779bb6cf | ||
|
|
660806fb7c | ||
|
|
40a52ed00b | ||
|
|
c216498198 | ||
|
|
547c887c0b | ||
|
|
dfe23da44d | ||
|
|
a5e8b88e32 | ||
|
|
539c94f36f | ||
|
|
fce5ae0498 | ||
|
|
8fbf96098e | ||
|
|
c8b43fa8ab | ||
|
|
9f305347a8 | ||
|
|
94d826d2a4 | ||
|
|
118cf70e40 | ||
|
|
895d8d594b | ||
|
|
60ce467859 | ||
|
|
6efa58006e | ||
|
|
f253019daf | ||
|
|
8600aceb64 | ||
|
|
a358af534a | ||
|
|
d080676997 | ||
|
|
b3412fcfb4 | ||
|
|
b5e3db279d | ||
|
|
a29ca46ca3 | ||
|
|
2fe54a28b1 | ||
|
|
6f9bc11078 | ||
|
|
cd5186cd0d | ||
|
|
34cdc299b7 | ||
|
|
274a3f78f2 | ||
|
|
0e31ad96a5 | ||
|
|
b191e4d83a | ||
|
|
16c554aa43 | ||
|
|
80550cd42e | ||
|
|
3ac28aa279 | ||
|
|
f16ea99cbc | ||
|
|
d4353bd237 | ||
|
|
8efa82abc2 | ||
|
|
a5f42f77ad | ||
|
|
384314d9ab | ||
|
|
56d63a46bf | ||
|
|
da00ccff58 | ||
|
|
313d5ab762 | ||
|
|
a569029c40 | ||
|
|
04748cf03f | ||
|
|
91871fb96d | ||
|
|
7292a12ff4 | ||
|
|
9b04e8ff38 | ||
|
|
b85a2ea6d2 | ||
|
|
da6c134540 | ||
|
|
e09da1a600 | ||
|
|
5aa1f9863d | ||
|
|
1eeb3637e8 | ||
|
|
79e229467e | ||
|
|
d483ea7f47 | ||
|
|
4e4b31a0d5 | ||
|
|
eb38c1ec15 | ||
|
|
b33406da4d | ||
|
|
10f4da71b8 | ||
|
|
6d27a8e489 | ||
|
|
c63477c34e | ||
|
|
761d1877fa | ||
|
|
cf4dbf6ed9 | ||
|
|
53e2df8aa2 | ||
|
|
07278de968 | ||
|
|
6710c47db0 | ||
|
|
a5cc820ea6 | ||
|
|
5eb95980bf | ||
|
|
57cc0e920a | ||
|
|
902b707b5e | ||
|
|
98600e9a19 | ||
|
|
fbf8ac4883 | ||
|
|
4c7a25407a | ||
|
|
af9359850d | ||
|
|
abe5638af5 | ||
|
|
c45e8a11d7 | ||
|
|
62ef84ea07 | ||
|
|
17b5098602 | ||
|
|
272a7afe9e | ||
|
|
9c7fa1a6a8 | ||
|
|
38a050dd13 | ||
|
|
377396d77c | ||
|
|
80fc8eddb9 | ||
|
|
d9c23ece15 | ||
|
|
84a913451f | ||
|
|
59794efc95 | ||
|
|
13977a07e5 | ||
|
|
b1eb8bd8ec | ||
|
|
69043cfd1c | ||
|
|
f34c713e7e | ||
|
|
e180bf21a7 | ||
|
|
fdaaa4be30 | ||
|
|
4db445dd3e | ||
|
|
530d4c0b08 | ||
|
|
100ce6ded9 | ||
|
|
03deb78aab | ||
|
|
bb220366be | ||
|
|
cd4e4a0892 | ||
|
|
aa42c302aa | ||
|
|
5128557e4e | ||
|
|
a181f5d1db | ||
|
|
5e3bc6c507 | ||
|
|
b11a9557d7 | ||
|
|
593e172832 | ||
|
|
6b62a7fde3 | ||
|
|
9a80028ec7 | ||
|
|
1252e22743 | ||
|
|
a3a25d9bef | ||
|
|
08fa3a5768 | ||
|
|
52cd521902 | ||
|
|
6dd5f1991f | ||
|
|
d3d29e03be | ||
|
|
2ddc88dd87 | ||
|
|
f768aa360f | ||
|
|
5ba12d009b | ||
|
|
6259891312 | ||
|
|
fad59e4a0f | ||
|
|
81852fe8c6 | ||
|
|
b33e262de6 | ||
|
|
7963919b72 | ||
|
|
e7f87478b4 | ||
|
|
c6b8d95747 | ||
|
|
8468c5ac52 | ||
|
|
27d7ae4b78 | ||
|
|
fffd2afe5e | ||
|
|
15ba670d5a | ||
|
|
7e7279a642 | ||
|
|
1b765e2408 | ||
|
|
db0ae9253e | ||
|
|
5573218794 | ||
|
|
6e38f16c83 | ||
|
|
4f0d2370ac | ||
|
|
f49f677107 | ||
|
|
cbb11ea723 | ||
|
|
566d011e2e | ||
|
|
1d2a62d06c | ||
|
|
743163fce7 | ||
|
|
afe5333c88 | ||
|
|
3bf15b5a35 | ||
|
|
1c9cfbc4bb | ||
|
|
65093b0388 | ||
|
|
e2320ea77c | ||
|
|
59f414f707 | ||
|
|
2a2c12cf77 | ||
|
|
5fd34dc967 | ||
|
|
65df6765da | ||
|
|
ff9729d27a | ||
|
|
9876741c50 | ||
|
|
a1f69f0732 | ||
|
|
8174b883a0 | ||
|
|
337065271e | ||
|
|
01dd0e7fc4 | ||
|
|
f3ee08ab9a | ||
|
|
ba08c09607 | ||
|
|
aecfd12911 | ||
|
|
c081e48467 | ||
|
|
237b0324c1 | ||
|
|
5aaf39f40b | ||
|
|
7a4ed29945 | ||
|
|
84014ce9b5 | ||
|
|
08c8e84d72 | ||
|
|
43171879a0 | ||
|
|
2f7a30f920 | ||
|
|
27a93b318d | ||
|
|
816bb2008a | ||
|
|
8eb430dffd | ||
|
|
641b5af8eb | ||
|
|
38ef1e4ed9 | ||
|
|
123c41b4e3 | ||
|
|
7001f17c88 | ||
|
|
1f10191bc7 | ||
|
|
ed5f5d3c6b | ||
|
|
a03bb7a353 | ||
|
|
44d4eae8a1 | ||
|
|
22ffcf5660 | ||
|
|
f40ea6cc8d | ||
|
|
adfcb2b4bf | ||
|
|
8d42be3bac | ||
|
|
e2036a5203 | ||
|
|
7107f05adb | ||
|
|
529fc42b72 | ||
|
|
7808605a8c | ||
|
|
0930013cc2 | ||
|
|
51054b70d6 | ||
|
|
471fb621e5 | ||
|
|
1e0c3e7621 | ||
|
|
a75d312770 | ||
|
|
0fd2ec58be | ||
|
|
be484a8077 | ||
|
|
3d8388ad92 | ||
|
|
7a5eac3621 | ||
|
|
ce34e937d6 | ||
|
|
c8646208f0 | ||
|
|
3ed83e4f59 | ||
|
|
ebe6a12555 | ||
|
|
1727456487 | ||
|
|
ef895777ca | ||
|
|
33fa10e9c1 | ||
|
|
1f9094caec | ||
|
|
9a5c58aae8 | ||
|
|
1b1e6c394d | ||
|
|
504872ac1e | ||
|
|
edc40f4a6c | ||
|
|
7b6e08f791 | ||
|
|
b184db2ee6 | ||
|
|
d10bf7fa35 | ||
|
|
ce20d21f4c | ||
|
|
48a35746a0 | ||
|
|
bf9afd4ba1 | ||
|
|
0eb2d59ecc | ||
|
|
b9bcfef817 | ||
|
|
27d01500fa | ||
|
|
d2ea3e4986 | ||
|
|
b0cecebe34 | ||
|
|
f4971589c6 | ||
|
|
506d64dc8b | ||
|
|
f56aa6cc29 | ||
|
|
a17ec8c2db | ||
|
|
a718986e2d | ||
|
|
242e0e7fdc | ||
|
|
75f7cb6df7 | ||
|
|
cf7d0792a2 | ||
|
|
29d260db9d | ||
|
|
d7dfad1fe8 | ||
|
|
82f404e627 | ||
|
|
c49b4c45c8 | ||
|
|
39f3feb210 | ||
|
|
4ed89e9515 | ||
|
|
0b6989c60c | ||
|
|
a9139c6023 | ||
|
|
2de580236e | ||
|
|
e5d4979f3e | ||
|
|
1023cc5cb2 | ||
|
|
094cf9e400 | ||
|
|
1bc834efe8 | ||
|
|
b4b039b2b0 | ||
|
|
d8e850673b | ||
|
|
35d7046d69 | ||
|
|
fad87abfea | ||
|
|
10d10ff18d | ||
|
|
161b99f252 | ||
|
|
87ff260c9f | ||
|
|
cc4dafa400 | ||
|
|
9d29904ec5 | ||
|
|
50be4f4ef8 | ||
|
|
5f9c05080f | ||
|
|
bbfcc87a68 | ||
|
|
d7e9510353 | ||
|
|
1a1057f56f | ||
|
|
93ff8cfe1b | ||
|
|
4d05e085ef | ||
|
|
d2d96d2ece | ||
|
|
78762cd479 | ||
|
|
cdaa37c5fd | ||
|
|
76140fb555 | ||
|
|
352c5ea3d3 | ||
|
|
f167d4fa3b | ||
|
|
7528cd3b58 | ||
|
|
cab8d0fb97 | ||
|
|
c7d9c174fd | ||
|
|
b7787c8cf6 | ||
|
|
4d218e3624 | ||
|
|
89fab45eb7 | ||
|
|
9bca338cc2 | ||
|
|
36c3c43f86 | ||
|
|
d9f762c485 | ||
|
|
3d206d1fb1 | ||
|
|
2a959f23d9 | ||
|
|
bb659d39db | ||
|
|
49cdd4d36d | ||
|
|
41d55cf780 | ||
|
|
5c5c948193 | ||
|
|
0e32dc982e | ||
|
|
9fffdb8cad | ||
|
|
76a1da2d0f | ||
|
|
ba012a4b0e | ||
|
|
09fd212e29 | ||
|
|
54fd4787fd | ||
|
|
023499a6c0 | ||
|
|
a8ad7f540b | ||
|
|
b7ee2c696b | ||
|
|
4528223dee | ||
|
|
cf729db0c0 | ||
|
|
a4deafa238 | ||
|
|
5e88eab617 | ||
|
|
950e51e0fe | ||
|
|
1c1d743638 | ||
|
|
ffa7e39121 | ||
|
|
84d6a24446 | ||
|
|
91c92d4b64 | ||
|
|
056229e8ee | ||
|
|
c4c5f7ab6b | ||
|
|
c7ab33f3ab | ||
|
|
3a3850ec4e | ||
|
|
84ed014909 | ||
|
|
39563682f5 | ||
|
|
53179f6e3f | ||
|
|
b1d5ba6787 | ||
|
|
361a3d0371 | ||
|
|
6b5b38535e | ||
|
|
8a4539b19a | ||
|
|
f6ee232d9e | ||
|
|
2562d28f4f | ||
|
|
f89cfa3ec8 | ||
|
|
d8b665f64f | ||
|
|
d473737ca8 | ||
|
|
52e7cc41e2 | ||
|
|
53978333fb | ||
|
|
183209baa3 | ||
|
|
0df4e1147c | ||
|
|
7c42b1f845 | ||
|
|
129e4762dc | ||
|
|
fc1c5ca6e9 | ||
|
|
8ae916ac28 | ||
|
|
0ccf9c961b | ||
|
|
b894e65618 | ||
|
|
801d31ed29 | ||
|
|
34fb9bb865 | ||
|
|
b5201e17ff | ||
|
|
204bbfc6c7 | ||
|
|
692fe0c885 | ||
|
|
3a372179d6 | ||
|
|
3de8bd49ad | ||
|
|
b9c02d5d99 | ||
|
|
185c4c09c9 | ||
|
|
ad709fd8f0 | ||
|
|
da7a1f34e1 | ||
|
|
47d2f3b6ad | ||
|
|
673759f17b | ||
|
|
0e2d6ac196 | ||
|
|
eb99915912 | ||
|
|
501df02260 | ||
|
|
d74f6dba0a | ||
|
|
b590c6b55a | ||
|
|
175f38f114 | ||
|
|
44f69b2b0b | ||
|
|
02a1b139bf | ||
|
|
326cf81a0f | ||
|
|
8952ddc9dc | ||
|
|
dfe2c6d064 | ||
|
|
fea2f30a1c | ||
|
|
5d8bd256f1 | ||
|
|
d7f41f4a56 | ||
|
|
9169ea6ebf | ||
|
|
20314c8952 | ||
|
|
f262075d2a | ||
|
|
37d9ea103b | ||
|
|
ef51f23308 | ||
|
|
11f75176b8 | ||
|
|
bd676c7c5c | ||
|
|
ccf51356a9 | ||
|
|
ece32cbe70 | ||
|
|
574e74350c | ||
|
|
4665f8c6c6 | ||
|
|
a1bdd1fc47 | ||
|
|
cdcb801b50 | ||
|
|
5f4bfed2b5 | ||
|
|
cffaa6fef8 | ||
|
|
bf2e5392f8 | ||
|
|
de6c93b849 | ||
|
|
3b6bbe4799 | ||
|
|
62250cd41f | ||
|
|
69a3963de0 | ||
|
|
acefebfcd7 | ||
|
|
b8f7d084c7 | ||
|
|
fd420b3d81 | ||
|
|
bf799869ef | ||
|
|
8fc3004349 | ||
|
|
13b22e2047 | ||
|
|
2a33ba9f93 | ||
|
|
ad09233d4c | ||
|
|
2be3a38bb0 | ||
|
|
26467221b3 | ||
|
|
dd19ad356a | ||
|
|
4fe91e9a6a | ||
|
|
63d660d152 | ||
|
|
730ff1758f | ||
|
|
174099dd1d | ||
|
|
f7f49b98ff | ||
|
|
18f0af1803 | ||
|
|
af2a4e1ce4 | ||
|
|
a4a58d9a06 | ||
|
|
861bb5f608 | ||
|
|
36c7f1244d | ||
|
|
8e4d07163d | ||
|
|
1af3cd40c0 | ||
|
|
8e0eb9d110 | ||
|
|
bf7dbe0098 | ||
|
|
f37d27f803 | ||
|
|
b98d2fbaa4 | ||
|
|
181539a424 | ||
|
|
6fe299d7c8 | ||
|
|
585eb23b3c | ||
|
|
04940fc4db | ||
|
|
75fa1b7fbe | ||
|
|
cc8e8b2dd1 | ||
|
|
0b3bbad98f | ||
|
|
9da11e05ee | ||
|
|
ecca6f8eda | ||
|
|
b5c6f32d1f | ||
|
|
f38c083b05 | ||
|
|
598a9e0918 | ||
|
|
c93c582ed5 | ||
|
|
010cfb6b35 | ||
|
|
b1d95a5fa0 | ||
|
|
0d4bf2ea2e | ||
|
|
d4eb451ba6 | ||
|
|
9435701d10 | ||
|
|
7603943c38 | ||
|
|
a959f79bcf | ||
|
|
9899d72dce | ||
|
|
4263ea6881 | ||
|
|
ab287858b3 | ||
|
|
eb6d168d50 | ||
|
|
52b8c4ae5e | ||
|
|
1c4640d69b | ||
|
|
3827459229 | ||
|
|
aaeb9f9e84 | ||
|
|
61acb3e42b | ||
|
|
4a8f299bb3 | ||
|
|
5530ca94f3 | ||
|
|
61a1b888bd | ||
|
|
a583701319 | ||
|
|
57623c710a | ||
|
|
93488528de | ||
|
|
e7a5ffdf5f | ||
|
|
dc56cb382b | ||
|
|
bb22ea3c8f | ||
|
|
b856ad0374 | ||
|
|
3aff3c51a8 | ||
|
|
3e096a25f7 | ||
|
|
adb21e4a7e | ||
|
|
3ea268aea7 | ||
|
|
567a8913e4 | ||
|
|
fd89b30192 | ||
|
|
ce3b327cff | ||
|
|
4e5ac4a921 | ||
|
|
b44c1478b8 | ||
|
|
b0890ad4bf | ||
|
|
83d632e24c | ||
|
|
1e5874aed5 | ||
|
|
e807d9a3e7 | ||
|
|
17bb74ff25 | ||
|
|
5c9e2839eb | ||
|
|
d670a761d9 | ||
|
|
b6a3f765a7 | ||
|
|
2ca2ceea5a | ||
|
|
01b4ab512c | ||
|
|
d18a6c3479 | ||
|
|
ab931cdcb4 | ||
|
|
7252b0e69a | ||
|
|
ab0ed23c83 | ||
|
|
18c002094f | ||
|
|
d5f73cb633 | ||
|
|
c738da065e | ||
|
|
a9296d67f7 | ||
|
|
78cb417cfc | ||
|
|
4bec5f6792 | ||
|
|
3da03f66e0 | ||
|
|
e379c676d0 | ||
|
|
6565494781 | ||
|
|
aafcd25403 | ||
|
|
72c4c0007f | ||
|
|
fd1ce7a750 | ||
|
|
fbbdec5e4e | ||
|
|
443b613513 | ||
|
|
2fa44fc0f4 | ||
|
|
5ed21c3ce3 | ||
|
|
f1d52fb530 | ||
|
|
d418ad8369 | ||
|
|
7b58d8e77e | ||
|
|
38971c2946 | ||
|
|
6cef7fd6b6 | ||
|
|
92ffb0b1e5 | ||
|
|
75f03f1fd1 | ||
|
|
0306817ac8 | ||
|
|
03b5dc046d | ||
|
|
bb5cb8a773 | ||
|
|
719bd257a6 | ||
|
|
e21257ab71 | ||
|
|
fc933ba9d5 | ||
|
|
9f9215cb3f | ||
|
|
8308c21d39 | ||
|
|
1f6ef5f045 | ||
|
|
7673918178 | ||
|
|
92b0aad937 | ||
|
|
233e126cd3 | ||
|
|
e9506787d5 | ||
|
|
e061289a26 | ||
|
|
7f87164235 | ||
|
|
0627d37f69 | ||
|
|
68dc49cb88 | ||
|
|
24cf97ca84 | ||
|
|
9859cbdca6 | ||
|
|
b819f303bb | ||
|
|
5ad4a25c33 | ||
|
|
265e5bb122 | ||
|
|
7964f862c6 | ||
|
|
622176d3de | ||
|
|
7e793a48e7 | ||
|
|
5bdc4615e8 | ||
|
|
ef04a4376a | ||
|
|
3c57007765 | ||
|
|
9965f2e79c | ||
|
|
f131997d6a | ||
|
|
08b9e3c46a | ||
|
|
8afbaefa6d | ||
|
|
88a8336ae7 | ||
|
|
0c6dface18 | ||
|
|
9bb2dd3887 | ||
|
|
1f3943fe9a | ||
|
|
9502034d58 | ||
|
|
c6b0b2021c | ||
|
|
0d889ec773 | ||
|
|
3d8f848936 | ||
|
|
6cbd108bca | ||
|
|
03c950f82e | ||
|
|
552b061357 | ||
|
|
ca98432e20 | ||
|
|
6ada1f04da | ||
|
|
edf1738e3c | ||
|
|
5b77205b77 | ||
|
|
67128c2fd5 | ||
|
|
490ec7ef3e | ||
|
|
0ab7ff919b | ||
|
|
489ccd9003 | ||
|
|
9971eef9b8 | ||
|
|
88e8bed395 | ||
|
|
9304942fd6 | ||
|
|
5f3b5cbe26 | ||
|
|
c5dbc8ddca | ||
|
|
eaea041e73 | ||
|
|
20b4ae230f | ||
|
|
037dce8be8 | ||
|
|
3ad6045377 | ||
|
|
3f683c3909 | ||
|
|
2f9974f6ef | ||
|
|
d2156a1179 | ||
|
|
d6a0b92f1e | ||
|
|
15aab58415 | ||
|
|
1e8679bf94 | ||
|
|
4eeb4c515f | ||
|
|
76a75942a9 | ||
|
|
4383c3486a | ||
|
|
e629877984 | ||
|
|
fb71ea83f0 | ||
|
|
d9cf090060 | ||
|
|
a0fd16513a | ||
|
|
c7c6d4e162 | ||
|
|
9a2eaa7a1d | ||
|
|
3b12843d5f | ||
|
|
6685649e39 | ||
|
|
7e63c902d3 | ||
|
|
c1e1530cd9 | ||
|
|
b1a190093c | ||
|
|
fcc7cf0566 | ||
|
|
ca3d51157f | ||
|
|
899a7b2500 | ||
|
|
d19be1731e | ||
|
|
90768b6933 | ||
|
|
89d6f050d1 | ||
|
|
5bfe7a2146 | ||
|
|
e54dc157ac | ||
|
|
5be7472dd9 | ||
|
|
1a2de2bbc2 | ||
|
|
0c9935b1b5 | ||
|
|
aba00ad17b | ||
|
|
5ca05394d6 | ||
|
|
5165415fb1 | ||
|
|
1f1060b97c | ||
|
|
3e9dabe2a5 | ||
|
|
86d72b15c5 | ||
|
|
3fd3a3e5aa | ||
|
|
3fbcf09326 | ||
|
|
d97cd9eabe | ||
|
|
de52bb1036 | ||
|
|
f9a073fd93 | ||
|
|
c77f99cf9b | ||
|
|
af8c1b2092 | ||
|
|
9095dcd9d8 | ||
|
|
08359103da | ||
|
|
9fe15fd8c2 | ||
|
|
728afbc887 | ||
|
|
1e10d49d0f | ||
|
|
4a81ea0ee4 | ||
|
|
acf89b1e9e | ||
|
|
1c99b5bbc7 | ||
|
|
d8d0c67462 | ||
|
|
2e9f050707 | ||
|
|
4b0a3741de | ||
|
|
c45c5f6e92 | ||
|
|
482515f538 | ||
|
|
42d75bd1ee | ||
|
|
296d7436ae | ||
|
|
5ba1466b96 | ||
|
|
7cdf36df87 | ||
|
|
02f25f8816 | ||
|
|
fa7e6bfa36 | ||
|
|
854890cc77 | ||
|
|
5445ec1b8d | ||
|
|
050f11bfab | ||
|
|
da89b95654 | ||
|
|
71631ccf0c | ||
|
|
f1c38dd742 | ||
|
|
0fc7126238 | ||
|
|
41cf366014 | ||
|
|
4288ae3582 | ||
|
|
e57d0a4d0e | ||
|
|
1b6a0188f3 | ||
|
|
e225b8a77c | ||
|
|
e4b1c542db | ||
|
|
b960460e02 | ||
|
|
c8f39802ae | ||
|
|
e3caad856d | ||
|
|
97bc512d1c | ||
|
|
5752fe698f | ||
|
|
727ef87a11 | ||
|
|
a92c85a0be | ||
|
|
225790cc4b | ||
|
|
0202d52e49 | ||
|
|
397ad4c597 | ||
|
|
ce00f31f0f | ||
|
|
b9dc9e6eb0 | ||
|
|
05884bba0d | ||
|
|
d8f37ac6a7 | ||
|
|
9c89e0b790 | ||
|
|
b37d695c8b | ||
|
|
263db54a12 | ||
|
|
dc8c25b8bc | ||
|
|
1d43cae1b1 | ||
|
|
7e02ae518d | ||
|
|
4afc439eb1 | ||
|
|
330d4f006f | ||
|
|
5929a7c6f9 | ||
|
|
1b95989b05 | ||
|
|
b284fc590e | ||
|
|
dd3e21e5d4 | ||
|
|
31c674c3dc | ||
|
|
b3632417a7 | ||
|
|
bc28919727 | ||
|
|
4333744248 | ||
|
|
d62599d62c | ||
|
|
bad745d2a9 | ||
|
|
0c6aa6afdb | ||
|
|
bb8e68d1b1 | ||
|
|
8c6b6e617c | ||
|
|
a9e4270ddb | ||
|
|
72262a77fd | ||
|
|
68c524a75d | ||
|
|
64c56288c9 | ||
|
|
2996bf7e6b | ||
|
|
5520c3778b | ||
|
|
4c6bdcb68a | ||
|
|
a925786899 | ||
|
|
e73a3cbf8c | ||
|
|
c86dbb4665 | ||
|
|
951c338edd | ||
|
|
5a22ad86b8 | ||
|
|
6db22d43a6 | ||
|
|
6569f59501 | ||
|
|
c30b04131b | ||
|
|
fb7e6d482a | ||
|
|
df252a0051 | ||
|
|
443d79f902 | ||
|
|
7ac9997950 | ||
|
|
aa59b5b4e3 | ||
|
|
2cd0b56f11 | ||
|
|
40c8485f02 | ||
|
|
f70e22fa4d | ||
|
|
cbaa7028d0 | ||
|
|
d6a4e33035 | ||
|
|
99bae93e07 | ||
|
|
9097fb6f60 | ||
|
|
8e9b91eeb6 | ||
|
|
419b46c7cc | ||
|
|
e927d76cb5 | ||
|
|
966d190b77 | ||
|
|
b7b06413d6 | ||
|
|
67ba5e21f7 | ||
|
|
27e401d3a1 | ||
|
|
45c1c40e9f | ||
|
|
29cfde5249 | ||
|
|
ffecb391b3 | ||
|
|
c87f8b0986 | ||
|
|
8cc4eef916 | ||
|
|
46f241f004 | ||
|
|
4dbe0bdbcb | ||
|
|
ee5e0beb7f | ||
|
|
ef89a35de6 | ||
|
|
26f94ccf07 | ||
|
|
c7a0b9d31c | ||
|
|
e2f91339b4 | ||
|
|
155b2723f1 | ||
|
|
2b5d5c5c12 | ||
|
|
660f854546 | ||
|
|
505ae78d14 | ||
|
|
0f19934021 | ||
|
|
0865e3262f | ||
|
|
8d266dbc4c | ||
|
|
8801365896 | ||
|
|
33249ea526 | ||
|
|
d1f3ec25fc | ||
|
|
8ea76ee1b8 | ||
|
|
660ed4c341 | ||
|
|
ef6f88a129 | ||
|
|
bf072e0802 | ||
|
|
d910521a0b | ||
|
|
3ceb40741c | ||
|
|
7d3a192435 | ||
|
|
50af4b83c5 | ||
|
|
2e4f79f6ef | ||
|
|
7c57be2181 | ||
|
|
2f238789ad | ||
|
|
29d4cfe669 | ||
|
|
df4eaa28b9 | ||
|
|
42dc55c664 | ||
|
|
a82ad37e78 | ||
|
|
5fdc7970cd | ||
|
|
8f45cc62d2 | ||
|
|
b8eaa369c2 | ||
|
|
fec2a4fcd5 | ||
|
|
f40ca2037b | ||
|
|
7227885e0e | ||
|
|
4fe66ddca8 | ||
|
|
5472f17c96 | ||
|
|
69b072f130 | ||
|
|
640106a78a | ||
|
|
2e31e1840a | ||
|
|
09334d2043 | ||
|
|
b52d22fd76 | ||
|
|
34233bf682 | ||
|
|
3ca111f6e0 | ||
|
|
09556cc3ac | ||
|
|
ff94d85e53 | ||
|
|
dd759d62e9 | ||
|
|
9739ed6b87 | ||
|
|
3d4c88ae7c | ||
|
|
eb5f842e7b | ||
|
|
6fb409dcd0 | ||
|
|
b2e36e8ca2 | ||
|
|
709524b7c9 | ||
|
|
4444438670 | ||
|
|
87ada19569 | ||
|
|
082fce33c3 | ||
|
|
ae93d2028a | ||
|
|
a36719686b | ||
|
|
981dc381cf | ||
|
|
261241c1ee | ||
|
|
7c8d432aa9 | ||
|
|
bd82d5d355 | ||
|
|
fed9270211 | ||
|
|
2fa0ef2572 | ||
|
|
02ffbb2b5c | ||
|
|
c00197d44a | ||
|
|
aa30390674 | ||
|
|
c54bf6f730 | ||
|
|
a5cc4cc01d | ||
|
|
ca02c342d7 | ||
|
|
bd58d81bcc | ||
|
|
15dfdb5bc1 | ||
|
|
36730dd6e2 | ||
|
|
0122589138 | ||
|
|
e82b1c4524 | ||
|
|
8a0ae86b28 | ||
|
|
d406277247 | ||
|
|
5e5015c552 | ||
|
|
22c46a50f0 | ||
|
|
7c9404864a | ||
|
|
82752f55d1 | ||
|
|
50a7643610 | ||
|
|
a85c84096f | ||
|
|
7edda12548 | ||
|
|
d91f1df9d8 | ||
|
|
7c2f9e529c | ||
|
|
d7ca06c599 | ||
|
|
f4ee0441ea | ||
|
|
7b28ea1fa3 | ||
|
|
e02da44288 | ||
|
|
e836880a39 | ||
|
|
2ef00808b7 | ||
|
|
1dca52e8cd | ||
|
|
a4f7bd4d2b | ||
|
|
5740917a11 | ||
|
|
0790220a44 | ||
|
|
462459c6c2 | ||
|
|
eee160c379 | ||
|
|
96c1ce5803 | ||
|
|
6dc705450b | ||
|
|
e9f5039817 | ||
|
|
4a7d0222a2 | ||
|
|
63e7803ea4 | ||
|
|
c522710f7f | ||
|
|
d9be2b2437 | ||
|
|
caeaefcc6a | ||
|
|
6048161924 | ||
|
|
4348bc20eb | ||
|
|
bff6d43701 | ||
|
|
944fc70ff4 | ||
|
|
a75a148278 | ||
|
|
a378eefb7b | ||
|
|
9782705e06 | ||
|
|
4cd47da3f4 | ||
|
|
65776d8401 | ||
|
|
1c5c5b3429 | ||
|
|
284d51918f | ||
|
|
06a1a49a73 | ||
|
|
1c2c72ca7c | ||
|
|
f2a28a34fe | ||
|
|
34e6eedf23 | ||
|
|
c1042d15b5 | ||
|
|
c40f258725 | ||
|
|
8619c180de | ||
|
|
1e9e5c5956 | ||
|
|
095769b7cd | ||
|
|
0740115724 | ||
|
|
862f2080ea | ||
|
|
2ed264b2b1 | ||
|
|
e9c2ea0e87 | ||
|
|
79cea28630 | ||
|
|
ec6ed8cea5 | ||
|
|
eab7a03c77 | ||
|
|
970d82e48e | ||
|
|
a9ad5169f2 | ||
|
|
27e0b3e62f | ||
|
|
3dfa2cd397 | ||
|
|
782eca7b95 | ||
|
|
17c6e46e8d | ||
|
|
684b3d2b2b | ||
|
|
e0c4ee102c | ||
|
|
0766a4b76d | ||
|
|
e16bd40fd3 | ||
|
|
479f72f160 | ||
|
|
56bfb32ad4 | ||
|
|
45dbb08993 | ||
|
|
5030f6e901 | ||
|
|
e64003a22f | ||
|
|
3abdeee850 | ||
|
|
dfec2dc308 | ||
|
|
abede2f979 | ||
|
|
695a0dac40 | ||
|
|
266e2e13cf | ||
|
|
79a81c86bb | ||
|
|
20f7bf5ebf | ||
|
|
bc3cd33a30 | ||
|
|
5eb3902854 | ||
|
|
eb75911d50 | ||
|
|
81b352c888 | ||
|
|
4b64dff6dd | ||
|
|
ea2eb39975 | ||
|
|
c3d64a7d2a | ||
|
|
4049a66c45 | ||
|
|
b06749aff9 | ||
|
|
6fa418b43b | ||
|
|
831786f55e | ||
|
|
952a9b69e1 | ||
|
|
8d367a1167 | ||
|
|
6cb366a978 | ||
|
|
aa99373d2d | ||
|
|
a6633365ec | ||
|
|
cac0f46d1c | ||
|
|
3e3b14779e | ||
|
|
225767acab | ||
|
|
7d1f31247e | ||
|
|
5fe1de5f7f | ||
|
|
0ac4f98ffa | ||
|
|
ebd3c72170 | ||
|
|
839a91d144 | ||
|
|
5c3df787bb | ||
|
|
daeec63b71 | ||
|
|
1aea0aa24c | ||
|
|
7b53957e55 | ||
|
|
09c7af4cc4 | ||
|
|
7ed9526828 | ||
|
|
27c61f1596 | ||
|
|
ca8e4d19bf | ||
|
|
938114f942 | ||
|
|
b7c61baafb | ||
|
|
5850e271c3 | ||
|
|
7ab15776df | ||
|
|
7f0b819993 | ||
|
|
c0af1fa1eb | ||
|
|
316eb681f6 | ||
|
|
2a841e4ae2 | ||
|
|
0033de0831 | ||
|
|
f70af84e16 | ||
|
|
d9571f3c4d | ||
|
|
fda2f1ff15 | ||
|
|
0d4824b7e0 | ||
|
|
0cd376e039 | ||
|
|
101c953020 | ||
|
|
46bf18cbfa | ||
|
|
5348cd3f22 | ||
|
|
9591ed744b | ||
|
|
efed8bbd4b | ||
|
|
7e0025d3fc | ||
|
|
b016b56a30 | ||
|
|
167d577f4d | ||
|
|
8888c97e07 | ||
|
|
b99e1f7b28 | ||
|
|
39b09c5b25 | ||
|
|
86842735bf | ||
|
|
05cf67d3e1 | ||
|
|
9f10d1b923 | ||
|
|
db66c6cde4 | ||
|
|
0eaa99a91a | ||
|
|
11de057064 | ||
|
|
d206ed5c0b | ||
|
|
6dd0275e5a | ||
|
|
cefaa78832 | ||
|
|
7253c56086 | ||
|
|
38dc4274ff | ||
|
|
833a5bd974 | ||
|
|
2443772e78 | ||
|
|
243a24c7f2 | ||
|
|
1166297dda | ||
|
|
58bfc4b36c | ||
|
|
1b07c8838f | ||
|
|
6592694778 | ||
|
|
08d5735af9 | ||
|
|
4cd69a52f6 | ||
|
|
542bab816f | ||
|
|
357be5296f | ||
|
|
57816a4f3a | ||
|
|
7486eb640f | ||
|
|
e946e23360 | ||
|
|
e233eed8eb | ||
|
|
80ac8906b3 | ||
|
|
ee9cb656fc | ||
|
|
cfe9f8e6e6 | ||
|
|
72f768cca4 | ||
|
|
9d0206c432 | ||
|
|
53bc91d399 | ||
|
|
ca63763d04 | ||
|
|
ac7589a3ff | ||
|
|
d752442aa3 | ||
|
|
930c27a890 | ||
|
|
2caec7be0e | ||
|
|
a7cfe98ca3 | ||
|
|
bad5f6f431 | ||
|
|
d0116c852c | ||
|
|
3f3ce39b30 | ||
|
|
8a5692e9df | ||
|
|
5cf96b1154 | ||
|
|
2d392de9ef | ||
|
|
f53aff1882 | ||
|
|
0cf6cbf3bc | ||
|
|
28b428a6f1 | ||
|
|
d311ced441 | ||
|
|
3ccce18f23 | ||
|
|
0ecd719799 | ||
|
|
5438e20b01 | ||
|
|
b5f72ecc5c | ||
|
|
dd20c8caf0 | ||
|
|
c3b96e4f68 | ||
|
|
b3132aa757 | ||
|
|
7685e3233f | ||
|
|
ee3e66580d | ||
|
|
26cd2acbcd | ||
|
|
0c3b099265 | ||
|
|
6971d320ae | ||
|
|
2dd6305e54 | ||
|
|
baf5479d0b | ||
|
|
c57be04143 | ||
|
|
f1030c2f10 | ||
|
|
1f1639e661 | ||
|
|
04d3445834 | ||
|
|
1559a14e39 | ||
|
|
fbbc6aa005 | ||
|
|
9c573b9bfd | ||
|
|
96bbe2c7dc | ||
|
|
ec9f6b82d9 | ||
|
|
2c120c3c45 | ||
|
|
b911f5c45b | ||
|
|
6366a22ff3 | ||
|
|
5fc819d8f5 | ||
|
|
df9d1ddcbf | ||
|
|
eed7b282d3 | ||
|
|
fc6237cd29 | ||
|
|
c473e634e0 | ||
|
|
28866fc1dc | ||
|
|
8028e8ca12 | ||
|
|
28d1cc2055 | ||
|
|
cffd99b68d | ||
|
|
ac92a92090 | ||
|
|
28dbb309cf | ||
|
|
7b585aaa05 | ||
|
|
82f0c85bd0 | ||
|
|
1c0a7d8475 | ||
|
|
5f63b5f907 | ||
|
|
7459cd65d9 | ||
|
|
0b61ddaff7 | ||
|
|
d554a7ee97 | ||
|
|
3d1e130b1e | ||
|
|
a63e008f84 | ||
|
|
d76ae27611 | ||
|
|
bf00d79e23 | ||
|
|
c08775f3e3 | ||
|
|
d94ceaf492 | ||
|
|
9802cb62d9 | ||
|
|
2c4f90b31c | ||
|
|
163d95800b | ||
|
|
0662d44a86 | ||
|
|
c3792191a2 | ||
|
|
97ff18c3e2 | ||
|
|
d069252f76 | ||
|
|
54fda151be | ||
|
|
04c4cf4426 | ||
|
|
7c81fe3339 | ||
|
|
330862e6c0 | ||
|
|
ce309a384e | ||
|
|
6bc01c3472 | ||
|
|
9ecf3e11bf | ||
|
|
7da306eb65 | ||
|
|
3d9d6d45fe | ||
|
|
7fc85a47be | ||
|
|
6accf7fd6c | ||
|
|
f619155667 | ||
|
|
89d50af521 | ||
|
|
fe3a111d6f | ||
|
|
75b6b6316b | ||
|
|
f64822a18b | ||
|
|
e566fb2d57 | ||
|
|
9f5931509c | ||
|
|
deb7ee8c3a | ||
|
|
6a56e29474 | ||
|
|
c79ccd7e23 | ||
|
|
36e395e3a3 | ||
|
|
c0b106c7ef | ||
|
|
23c8c5da19 | ||
|
|
883f1a7cf8 | ||
|
|
b2ff43bd9b | ||
|
|
471bb1cacd | ||
|
|
de30329a58 | ||
|
|
c990c8af56 | ||
|
|
2dbd099c68 | ||
|
|
25da363d56 | ||
|
|
a957566fc9 | ||
|
|
97db42427e | ||
|
|
34fabaab5f | ||
|
|
11893e244e | ||
|
|
24f1285a43 | ||
|
|
2d2e0fd6c0 | ||
|
|
5ea2aca8a8 | ||
|
|
088b4fa07e | ||
|
|
7dcfbc409c | ||
|
|
44bb2ff0f5 | ||
|
|
4effb02219 | ||
|
|
5d7ad6bb81 | ||
|
|
2bef5d7653 | ||
|
|
6b76bec480 | ||
|
|
76ab53078e | ||
|
|
ddf8df9c71 | ||
|
|
b312f28ab6 | ||
|
|
d9e536b789 | ||
|
|
2e768cb77b | ||
|
|
d611dbd386 | ||
|
|
8976e238a5 | ||
|
|
eb13bb8b6d | ||
|
|
d72eba1602 | ||
|
|
cd4685c093 | ||
|
|
bc3aa56ed9 | ||
|
|
903ead9e91 | ||
|
|
bacbd8baf3 | ||
|
|
407c742124 | ||
|
|
34a91b7e3d | ||
|
|
65f4724f87 | ||
|
|
afafe46221 | ||
|
|
ff220113af | ||
|
|
81c2c482cd | ||
|
|
afb06baa56 | ||
|
|
d25ff61f22 | ||
|
|
067f0b3a66 | ||
|
|
0f71efd1e9 | ||
|
|
351f9c1976 | ||
|
|
a11eff4d9a | ||
|
|
fe6a5a700a | ||
|
|
87a994b8ce | ||
|
|
3f06553604 | ||
|
|
e9f765a216 | ||
|
|
532e65f3a0 | ||
|
|
b038044347 | ||
|
|
0664d11f55 | ||
|
|
6d00a26530 | ||
|
|
a4d28b13ef | ||
|
|
fd01dc1bb1 | ||
|
|
fa72d3d0de | ||
|
|
f935f57d23 | ||
|
|
fbf1e855ad | ||
|
|
50a6037a6d | ||
|
|
d15d27defb | ||
|
|
7952bdcb48 | ||
|
|
dfdf3160d3 | ||
|
|
ce4f12179c | ||
|
|
491da787b4 | ||
|
|
6dbc7e2704 | ||
|
|
2a39c917ee | ||
|
|
3733ba45e2 | ||
|
|
2a410b9099 | ||
|
|
3ccc7c41e7 | ||
|
|
d03bc797fc | ||
|
|
5c4f8eb526 | ||
|
|
973464f1c4 | ||
|
|
e090ab2825 | ||
|
|
0413e5ded8 | ||
|
|
51c24f6fdf | ||
|
|
d1de248894 | ||
|
|
fdf3e618c2 | ||
|
|
5b0a228f4f | ||
|
|
9db11ce690 | ||
|
|
f9bdf6e181 |
98
README.md
98
README.md
@@ -1,15 +1,15 @@
|
||||
# Forge
|
||||
|
||||
Gitlab repo is found [here](https://git.cardforge.org/core-developers/forge).
|
||||
[Official GitLab repo](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
|
||||
## Requirements / Tools
|
||||
|
||||
- Java IDE such as IntelliJ or Eclipse
|
||||
- Java JDK 8 or later
|
||||
- you favourite Java IDE (IntelliJ, Eclipse, VSCodium, Emacs, Vi...)
|
||||
- Java JDK 8 or later (some IDEs such as Eclipse require JDK11+, whereas the Android build currently only works with JDK8)
|
||||
- Git
|
||||
- Git client (optional)
|
||||
- Maven
|
||||
@@ -18,7 +18,7 @@ Discord channel [here](https://discordapp.com/channels/267367946135928833/267742
|
||||
- Android SDK (optional: for Android releases)
|
||||
- RoboVM (optional: for iOS releases) (TBD: Current status of support by libgdx)
|
||||
|
||||
# Project Quick Setup
|
||||
## Project Quick Setup
|
||||
|
||||
- Log in to gitlab with your user account and fork the project.
|
||||
|
||||
@@ -26,18 +26,18 @@ Discord channel [here](https://discordapp.com/channels/267367946135928833/267742
|
||||
|
||||
- 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
|
||||
|
||||
Eclipse includes Maven integration so a separate install is not necessary. For other IDEs, your mileage may vary.
|
||||
|
||||
## Project Setup
|
||||
### Project Setup
|
||||
|
||||
- Follow the instructions for cloning from Gitlab. You'll need a Gitlab account setup and an SSH key defined.
|
||||
- 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.
|
||||
@@ -48,16 +48,16 @@ Eclipse includes Maven integration so a separate install is not necessary. For
|
||||
|
||||
- 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
|
||||
### Project Launch
|
||||
|
||||
### Desktop
|
||||
#### Desktop
|
||||
|
||||
This is the standard configuration used for releasing to Windows / Linux / MacOS.
|
||||
|
||||
@@ -65,7 +65,7 @@ This is the standard configuration used for releasing to Windows / Linux / MacOS
|
||||
|
||||
- The familiar Forge splash screen, etc. should appear. Enjoy!
|
||||
|
||||
### Mobile (Desktop dev)
|
||||
#### 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.
|
||||
|
||||
@@ -73,50 +73,49 @@ This is the configuration used for doing mobile development using the Windows /
|
||||
|
||||
- A view similar to a mobile phone should appear. Enjoy!
|
||||
|
||||
## Eclipse / Android SDK Integration
|
||||
### 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
|
||||
#### 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
|
||||
##### 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
|
||||
##### Linux / Mac OSX
|
||||
|
||||
TBD
|
||||
|
||||
### Android Plugin for Eclipse
|
||||
#### 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
|
||||
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
|
||||
#### 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
|
||||
- Android 8.0.0 (API 26) SDK Platform
|
||||
- Google USB Driver (in case your phone is not detected by ADB)
|
||||
|
||||
Note that this will populate additional tools in the Android SDK install path extracted above.
|
||||
|
||||
### Proguard update
|
||||
#### 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/.
|
||||
The Proguard included with the Android SDK Build-tools is outdated and does not work with Java 1.8. Download Proguard 6.0.3 or later (last tested with 7.0.1) from https://github.com/Guardsquare/proguard
|
||||
- Go to the Android SDK install path. Rename the tools/proguard/ path to tools/proguard-4.7/.
|
||||
|
||||
- Go to the Android SDK install path. Rename the tools/proguard/ path to tools/proguard4.7/.
|
||||
- Extract your Proguard version to the Android SDK install path under tools/. You will need to either rename the dir proguard-<your-version> to proguard/ or, if your filesystem supports it, use a symbolic link (the later is highly recommended), such as `ln -s proguard proguard-<your-version>`.
|
||||
|
||||
- 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
|
||||
#### 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.
|
||||
@@ -135,7 +134,7 @@ things out. The steps below show how to generate a debug Android build.
|
||||
|
||||
Assuming you got this far, you should have an Android forge-android-[version].apk in the forge-gui-android/target path.
|
||||
|
||||
### Android Deploy
|
||||
#### Android Deploy
|
||||
|
||||
You'll need to have the Android SDK install path platform-tools/ path in your command search path to easily deploy builds.
|
||||
|
||||
@@ -149,37 +148,37 @@ You'll need to have the Android SDK install path platform-tools/ path in your co
|
||||
|
||||
- Install the new apk: `adb install forge-android-[version].apk`
|
||||
|
||||
### Android Debugging
|
||||
#### 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
|
||||
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
|
||||
### 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
|
||||
## IntelliJ
|
||||
|
||||
Quick start guide for [setting up the Forge project within IntelliJ](https://git.cardforge.org/core-developers/forge/wikis/intellij-setup).
|
||||
|
||||
# Card Scripting
|
||||
## 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
|
||||
## General Notes
|
||||
|
||||
## Project Hierarchy
|
||||
### Project Hierarchy
|
||||
|
||||
Forge is divided into 4 primary projects with additional projects that target specific platform releases. The primary projects are:
|
||||
|
||||
@@ -196,35 +195,34 @@ The platform-specific projects are:
|
||||
- forge-gui-mobile
|
||||
- forge-gui-mobile-dev
|
||||
|
||||
### forge-ai
|
||||
#### forge-ai
|
||||
|
||||
### forge-core
|
||||
#### forge-core
|
||||
|
||||
### forge-game
|
||||
#### forge-game
|
||||
|
||||
### forge-gui
|
||||
#### forge-gui
|
||||
|
||||
The forge-gui project includes the scripting resource definitions in the res/ path.
|
||||
|
||||
### forge-gui-android
|
||||
#### forge-gui-android
|
||||
|
||||
Libgdx-based backend targeting Android. Requires Android SDK and relies on forge-gui-mobile for GUI logic.
|
||||
|
||||
### forge-gui-desktop
|
||||
#### forge-gui-desktop
|
||||
|
||||
Java Swing based GUI targeting desktop machines.
|
||||
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
|
||||
#### forge-gui-ios
|
||||
|
||||
Libgdx-based backend targeting iOS. Relies on forge-gui-mobile for GUI logic.
|
||||
|
||||
### forge-gui-mobile
|
||||
#### 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
|
||||
#### forge-gui-mobile-dev
|
||||
|
||||
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.40-SNAPSHOT</version>
|
||||
<version>1.6.40</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
|
||||
@@ -91,7 +91,7 @@ public class AiAttackController {
|
||||
|
||||
public AiAttackController(final Player ai, boolean nextTurn) {
|
||||
this.ai = ai;
|
||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
||||
this.defendingOpponent = choosePreferredDefenderPlayer(ai);
|
||||
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||
this.myList = ai.getCreaturesInPlay();
|
||||
this.attackers = new ArrayList<>();
|
||||
@@ -107,7 +107,7 @@ public class AiAttackController {
|
||||
|
||||
public AiAttackController(final Player ai, Card attacker) {
|
||||
this.ai = ai;
|
||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
||||
this.defendingOpponent = choosePreferredDefenderPlayer(ai);
|
||||
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||
this.myList = ai.getCreaturesInPlay();
|
||||
this.attackers = new ArrayList<>();
|
||||
@@ -156,13 +156,12 @@ public class AiAttackController {
|
||||
}
|
||||
|
||||
/** Choose opponent for AI to attack here. Expand as necessary. */
|
||||
private Player choosePreferredDefenderPlayer() {
|
||||
Player defender = ai.getWeakestOpponent(); //Gets opponent with the least life
|
||||
public static Player choosePreferredDefenderPlayer(Player ai) {
|
||||
Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range
|
||||
|
||||
if (defender.getLife() < 8) { //Concentrate on opponent within easy kill range
|
||||
return defender;
|
||||
} else { //Otherwise choose a random opponent to ensure no ganging up on players
|
||||
defender = ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
|
||||
if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players
|
||||
// TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked
|
||||
return ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
|
||||
}
|
||||
return defender;
|
||||
}
|
||||
@@ -624,7 +623,7 @@ public class AiAttackController {
|
||||
int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage;
|
||||
int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, opp);
|
||||
|
||||
if (totalCombatDamage + ComputerUtil.possibleNonCombatDamage(ai) >= opp.getLife()
|
||||
if (totalCombatDamage + ComputerUtil.possibleNonCombatDamage(ai, opp) >= opp.getLife()
|
||||
&& !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && opp.getLife() < 1)) {
|
||||
return true;
|
||||
}
|
||||
@@ -919,7 +918,7 @@ public class AiAttackController {
|
||||
// find the potential damage ratio the AI can cause
|
||||
double humanLifeToDamageRatio = 1000000;
|
||||
if (candidateUnblockedDamage > 0) {
|
||||
humanLifeToDamageRatio = (double) (opp.getLife() - ComputerUtil.possibleNonCombatDamage(ai)) / candidateUnblockedDamage;
|
||||
humanLifeToDamageRatio = (double) (opp.getLife() - ComputerUtil.possibleNonCombatDamage(ai, opp)) / candidateUnblockedDamage;
|
||||
}
|
||||
|
||||
// determine if the ai outnumbers the player
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.ability.ChangeZoneAi;
|
||||
import forge.ai.ability.ExploreAi;
|
||||
import forge.ai.ability.LearnAi;
|
||||
import forge.ai.simulation.SpellAbilityPicker;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
@@ -88,6 +89,7 @@ import forge.game.spellability.SpellAbilityCondition;
|
||||
import forge.game.spellability.SpellAbilityPredicates;
|
||||
import forge.game.spellability.SpellPermanent;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityMustTarget;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.trigger.WrappedAbility;
|
||||
@@ -826,8 +828,11 @@ public class AiController {
|
||||
}
|
||||
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
|
||||
}
|
||||
if (sa.usesTargeting() && !sa.isTargetNumberValid()) {
|
||||
if (!sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||
if (sa.usesTargeting()) {
|
||||
if (!sa.isTargetNumberValid() && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||
return AiPlayDecision.TargetingFailed;
|
||||
}
|
||||
if (!StaticAbilityMustTarget.meetsMustTargetRestriction(sa)) {
|
||||
return AiPlayDecision.TargetingFailed;
|
||||
}
|
||||
}
|
||||
@@ -1110,12 +1115,18 @@ public class AiController {
|
||||
final CardCollection discardList = new CardCollection();
|
||||
int count = 0;
|
||||
if (sa != null) {
|
||||
String logic = sa.getParamOrDefault("AILogic", "");
|
||||
sourceCard = sa.getHostCard();
|
||||
if ("Always".equals(sa.getParam("AILogic")) && !validCards.isEmpty()) {
|
||||
if ("Always".equals(logic) && !validCards.isEmpty()) {
|
||||
min = 1;
|
||||
} else if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) {
|
||||
} else if (logic.startsWith("UnlessAtLife.")) {
|
||||
int threshold = AbilityUtils.calculateAmount(sourceCard, logic.substring(logic.indexOf(".") + 1), sa);
|
||||
if (player.getLife() <= threshold) {
|
||||
min = 1;
|
||||
}
|
||||
} else if ("VolrathsShapeshifter".equals(logic)) {
|
||||
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
|
||||
} else if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
||||
} else if ("DiscardCMCX".equals(logic)) {
|
||||
final int cmc = sa.getXManaCostPaid();
|
||||
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc));
|
||||
if (discards.isEmpty()) {
|
||||
@@ -2089,8 +2100,11 @@ public class AiController {
|
||||
if (useSimulation) {
|
||||
return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
||||
}
|
||||
|
||||
if (sa.getApi() == ApiType.Explore) {
|
||||
return ExploreAi.shouldPutInGraveyard(fetchList, decider);
|
||||
} else if (sa.getApi() == ApiType.Learn) {
|
||||
return LearnAi.chooseCardToLearn(fetchList, decider, sa);
|
||||
} else {
|
||||
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import forge.game.cost.*;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
@@ -23,36 +24,6 @@ import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.CostAddMana;
|
||||
import forge.game.cost.CostChooseCreatureType;
|
||||
import forge.game.cost.CostDamage;
|
||||
import forge.game.cost.CostDecisionMakerBase;
|
||||
import forge.game.cost.CostDiscard;
|
||||
import forge.game.cost.CostDraw;
|
||||
import forge.game.cost.CostExert;
|
||||
import forge.game.cost.CostExile;
|
||||
import forge.game.cost.CostExileFromStack;
|
||||
import forge.game.cost.CostExiledMoveToGrave;
|
||||
import forge.game.cost.CostFlipCoin;
|
||||
import forge.game.cost.CostGainControl;
|
||||
import forge.game.cost.CostGainLife;
|
||||
import forge.game.cost.CostMill;
|
||||
import forge.game.cost.CostPartMana;
|
||||
import forge.game.cost.CostPayEnergy;
|
||||
import forge.game.cost.CostPayLife;
|
||||
import forge.game.cost.CostPutCardToLib;
|
||||
import forge.game.cost.CostPutCounter;
|
||||
import forge.game.cost.CostRemoveAnyCounter;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
import forge.game.cost.CostReturn;
|
||||
import forge.game.cost.CostReveal;
|
||||
import forge.game.cost.CostSacrifice;
|
||||
import forge.game.cost.CostTap;
|
||||
import forge.game.cost.CostTapType;
|
||||
import forge.game.cost.CostUnattach;
|
||||
import forge.game.cost.CostUntap;
|
||||
import forge.game.cost.CostUntapType;
|
||||
import forge.game.cost.PaymentDecision;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -577,6 +548,11 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostRevealChosenPlayer cost) {
|
||||
return PaymentDecision.number(1);
|
||||
}
|
||||
|
||||
protected int removeCounter(GameEntityCounterTable table, List<Card> prefs, CounterEnumType cType, int stillToRemove) {
|
||||
int removed = 0;
|
||||
if (!prefs.isEmpty() && stillToRemove > 0) {
|
||||
@@ -590,7 +566,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove);
|
||||
if (thisRemove > 0) {
|
||||
removed += thisRemove;
|
||||
table.put(prefCard, CounterType.get(cType), thisRemove);
|
||||
table.put(null, prefCard, CounterType.get(cType), thisRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -656,7 +632,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
int thisRemove = Math.min(card.getCounters(ctype), c - toRemove);
|
||||
if (thisRemove > 0) {
|
||||
toRemove += thisRemove;
|
||||
table.put(card, ctype, thisRemove);
|
||||
table.put(null, card, ctype, thisRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -684,7 +660,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
int over = Math.min(e.getValue(), c - toRemove);
|
||||
if (over > 0) {
|
||||
toRemove += over;
|
||||
table.put(crd, e.getKey(), over);
|
||||
table.put(null, crd, e.getKey(), over);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -714,7 +690,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
int over = Math.min(e.getValue(), c - toRemove);
|
||||
if (over > 0) {
|
||||
toRemove += over;
|
||||
table.put(crd, e.getKey(), over);
|
||||
table.put(null, crd, e.getKey(), over);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -754,7 +730,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove);
|
||||
if (over > 0) {
|
||||
toRemove += over;
|
||||
table.put(crd, CounterType.get(CounterEnumType.QUEST), over);
|
||||
table.put(null, crd, CounterType.get(CounterEnumType.QUEST), over);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -778,7 +754,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
int thisRemove = Math.min(card.getCounters(cost.counter), c - toRemove);
|
||||
if (thisRemove > 0) {
|
||||
toRemove += thisRemove;
|
||||
table.put(card, cost.counter, thisRemove);
|
||||
table.put(null, card, cost.counter, thisRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -792,7 +768,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
int thisRemove = Math.min(e.getValue(), c - toRemove);
|
||||
if (thisRemove > 0) {
|
||||
toRemove += thisRemove;
|
||||
table.put(card, e.getKey(), thisRemove);
|
||||
table.put(null, card, e.getKey(), thisRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,6 @@ public class ComputerUtil {
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
|
||||
// Play higher costing spells first?
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
@@ -213,7 +212,8 @@ public class ComputerUtil {
|
||||
if (unless != null && !unless.endsWith(">")) {
|
||||
final int amount = AbilityUtils.calculateAmount(source, unless, sa);
|
||||
|
||||
final int usableManaSources = ComputerUtilMana.getAvailableManaSources(ComputerUtil.getOpponentFor(ai), true).size();
|
||||
// this is enough as long as the AI is only smart enough to target top of stack
|
||||
final int usableManaSources = ComputerUtilMana.getAvailableManaSources(ComputerUtilAbility.getTopSpellAbilityOnStack(ai.getGame(), sa).getActivatingPlayer(), true).size();
|
||||
|
||||
// If the Unless isn't enough, this should be less likely to be used
|
||||
if (amount > usableManaSources) {
|
||||
@@ -1068,9 +1068,6 @@ public class ComputerUtil {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (card.isEquipment() && buffedcard.isCreature() && CombatUtil.canAttack(buffedcard, ComputerUtil.getOpponentFor(ai))) {
|
||||
return true;
|
||||
}
|
||||
if (card.isCreature()) {
|
||||
if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
|
||||
return true;
|
||||
@@ -1093,8 +1090,8 @@ public class ComputerUtil {
|
||||
|
||||
} // BuffedBy
|
||||
|
||||
// get all cards the human controls with AntiBuffedBy
|
||||
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
||||
// there's a good chance AI will attack weak target
|
||||
final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
|
||||
for (Card buffedcard : antibuffed) {
|
||||
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
||||
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
||||
@@ -1142,27 +1139,16 @@ public class ComputerUtil {
|
||||
* @return true if it's OK to cast this Card for less than the max targets
|
||||
*/
|
||||
public static boolean shouldCastLessThanMax(final Player ai, final Card source) {
|
||||
boolean ret = true;
|
||||
if (source.getManaCost().countX() > 0) {
|
||||
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available.
|
||||
return ret;
|
||||
} else {
|
||||
// Otherwise, if life is possibly in danger, then this is fine.
|
||||
Combat combat = new Combat(ComputerUtil.getOpponentFor(ai));
|
||||
CardCollectionView attackers = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
|
||||
for (Card att : attackers) {
|
||||
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
||||
combat.addAttacker(att, ComputerUtil.getOpponentFor(att.getController()));
|
||||
}
|
||||
}
|
||||
AiBlockController aiBlock = new AiBlockController(ai);
|
||||
aiBlock.assignBlockersForCombat(combat);
|
||||
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
// Otherwise, return false. Do not play now.
|
||||
ret = false;
|
||||
}
|
||||
if (source.getXManaCostPaid() > 0) {
|
||||
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by payment resources available.
|
||||
return true;
|
||||
}
|
||||
return ret;
|
||||
if (aiLifeInDanger(ai, false, 0)) {
|
||||
// Otherwise, if life is possibly in danger, then this is fine.
|
||||
return true;
|
||||
}
|
||||
// do not play now.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1266,8 +1252,8 @@ public class ComputerUtil {
|
||||
}
|
||||
}
|
||||
|
||||
// get all cards the human controls with AntiBuffedBy
|
||||
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
||||
// there's a good chance AI will attack weak target
|
||||
final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
|
||||
for (Card buffedcard : antibuffed) {
|
||||
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
||||
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
||||
@@ -1463,7 +1449,7 @@ public class ComputerUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int possibleNonCombatDamage(Player ai) {
|
||||
public static int possibleNonCombatDamage(Player ai, Player enemy) {
|
||||
int damage = 0;
|
||||
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||
all.addAll(ai.getCardsActivableInExternalZones(true));
|
||||
@@ -1483,7 +1469,6 @@ public class ComputerUtil {
|
||||
if (tgt == null) {
|
||||
continue;
|
||||
}
|
||||
final Player enemy = ComputerUtil.getOpponentFor(ai);
|
||||
if (!sa.canTarget(enemy)) {
|
||||
continue;
|
||||
}
|
||||
@@ -2346,7 +2331,7 @@ public class ComputerUtil {
|
||||
}
|
||||
}
|
||||
else if (logic.equals("ChosenLandwalk")) {
|
||||
for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) {
|
||||
for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
|
||||
for (String t : c.getType()) {
|
||||
if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) {
|
||||
chosen = t;
|
||||
@@ -2364,7 +2349,7 @@ public class ComputerUtil {
|
||||
else if (kindOfType.equals("Land")) {
|
||||
if (logic != null) {
|
||||
if (logic.equals("ChosenLandwalk")) {
|
||||
for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) {
|
||||
for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
|
||||
for (String t : c.getType().getLandTypes()) {
|
||||
if (!invalidTypes.contains(t)) {
|
||||
chosen = t;
|
||||
@@ -2399,23 +2384,26 @@ public class ComputerUtil {
|
||||
case "Torture":
|
||||
return "Torture";
|
||||
case "GraceOrCondemnation":
|
||||
return ai.getCreaturesInPlay().size() > ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size() ? "Grace"
|
||||
: "Condemnation";
|
||||
List<ZoneType> graceZones = new ArrayList<ZoneType>();
|
||||
graceZones.add(ZoneType.Battlefield);
|
||||
graceZones.add(ZoneType.Graveyard);
|
||||
CardCollection graceCreatures = CardLists.getType(sa.getHostCard().getGame().getCardsIn(graceZones), "Creature");
|
||||
int humanGrace = CardLists.filterControlledBy(graceCreatures, ai.getOpponents()).size();
|
||||
int aiGrace = CardLists.filterControlledBy(graceCreatures, ai).size();
|
||||
return aiGrace > humanGrace ? "Grace" : "Condemnation";
|
||||
case "CarnageOrHomage":
|
||||
CardCollection cardsInPlay = CardLists
|
||||
.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
|
||||
CardCollection cardsInPlay = CardLists.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
|
||||
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
|
||||
CardCollection computerlist = CardLists.filterControlledBy(cardsInPlay, ai);
|
||||
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard
|
||||
.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
|
||||
CardCollection computerlist = ai.getCreaturesInPlay();
|
||||
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
|
||||
case "Judgment":
|
||||
if (votes.isEmpty()) {
|
||||
CardCollection list = new CardCollection();
|
||||
for (Object o : options) {
|
||||
if (o instanceof Card) {
|
||||
list.add((Card) o);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ComputerUtilCard.getBestAI(list);
|
||||
} else {
|
||||
return Iterables.getFirst(votes.keySet(), null);
|
||||
@@ -2934,23 +2922,6 @@ public class ComputerUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Deprecated
|
||||
public static final Player getOpponentFor(final Player player) {
|
||||
// This method is deprecated and currently functions as a synonym for player.getWeakestOpponent
|
||||
// until it can be replaced everywhere in the code.
|
||||
|
||||
// Consider replacing calls to this method either with a multiplayer-friendly determination of
|
||||
// opponent that contextually makes the most sense, or with a direct call to player.getWeakestOpponent
|
||||
// where that is applicable and makes sense from the point of view of multiplayer AI logic.
|
||||
Player opponent = player.getWeakestOpponent();
|
||||
if (opponent != null) {
|
||||
return opponent;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("No opponents left ingame for " + player);
|
||||
}
|
||||
|
||||
public static int countUsefulCreatures(Player p) {
|
||||
CardCollection creats = p.getCreaturesInPlay();
|
||||
int count = 0;
|
||||
@@ -3033,31 +3004,32 @@ public class ComputerUtil {
|
||||
// call this to determine if it's safe to use a life payment spell
|
||||
// or trigger "emergency" strategies such as holding mana for Spike Weaver of Counterspell.
|
||||
public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) {
|
||||
Player opponent = ComputerUtil.getOpponentFor(ai);
|
||||
// test whether the human can kill the ai next turn
|
||||
Combat combat = new Combat(opponent);
|
||||
boolean containsAttacker = false;
|
||||
for (Card att : opponent.getCreaturesInPlay()) {
|
||||
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
||||
combat.addAttacker(att, ai);
|
||||
containsAttacker = true;
|
||||
for (Player opponent: ai.getOpponents()) {
|
||||
// test whether the human can kill the ai next turn
|
||||
Combat combat = new Combat(opponent);
|
||||
boolean containsAttacker = false;
|
||||
for (Card att : opponent.getCreaturesInPlay()) {
|
||||
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
||||
combat.addAttacker(att, ai);
|
||||
containsAttacker = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!containsAttacker) {
|
||||
return false;
|
||||
}
|
||||
AiBlockController block = new AiBlockController(ai);
|
||||
block.assignBlockersForCombat(combat);
|
||||
if (!containsAttacker) {
|
||||
return false;
|
||||
}
|
||||
AiBlockController block = new AiBlockController(ai);
|
||||
block.assignBlockersForCombat(combat);
|
||||
|
||||
// TODO predict other, noncombat sources of damage and add them to the "payment" variable.
|
||||
// examples : Black Vise, The Rack, known direct damage spells in enemy hand, etc
|
||||
// If added, might need a parameter to define whether we want to check all threats or combat threats.
|
||||
// TODO predict other, noncombat sources of damage and add them to the "payment" variable.
|
||||
// examples : Black Vise, The Rack, known direct damage spells in enemy hand, etc
|
||||
// If added, might need a parameter to define whether we want to check all threats or combat threats.
|
||||
|
||||
if ((serious) && (ComputerUtilCombat.lifeInSeriousDanger(ai, combat, payment))) {
|
||||
return true;
|
||||
}
|
||||
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
|
||||
return true;
|
||||
if ((serious) && (ComputerUtilCombat.lifeInSeriousDanger(ai, combat, payment))) {
|
||||
return true;
|
||||
}
|
||||
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
|
||||
@@ -550,7 +550,7 @@ public class ComputerUtilCard {
|
||||
*/
|
||||
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
|
||||
AiBlockController aiBlk = new AiBlockController(ai);
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
Combat combat = new Combat(opp);
|
||||
//Use actual attackers if available, else consider all possible attackers
|
||||
Combat currentCombat = ai.getGame().getCombat();
|
||||
|
||||
@@ -97,34 +97,39 @@ public class ComputerUtilCombat {
|
||||
* canAttackNextTurn.
|
||||
* </p>
|
||||
*
|
||||
* @param atacker
|
||||
* @param attacker
|
||||
* a {@link forge.game.card.Card} object.
|
||||
* @param defender
|
||||
* the defending {@link GameEntity}.
|
||||
* @return a boolean.
|
||||
*/
|
||||
public static boolean canAttackNextTurn(final Card atacker, final GameEntity defender) {
|
||||
if (!atacker.isCreature()) {
|
||||
public static boolean canAttackNextTurn(final Card attacker, final GameEntity defender) {
|
||||
if (!attacker.isCreature()) {
|
||||
return false;
|
||||
}
|
||||
if (!CombatUtil.canAttackNextTurn(atacker, defender)) {
|
||||
if (!CombatUtil.canAttackNextTurn(attacker, defender)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (final KeywordInterface inst : atacker.getKeywords()) {
|
||||
for (final KeywordInterface inst : attacker.getKeywords()) {
|
||||
final String keyword = inst.getOriginal();
|
||||
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
|
||||
final String defined = keyword.split(":")[1];
|
||||
final Player player = AbilityUtils.getDefinedPlayers(atacker, defined, null).get(0);
|
||||
final Player player = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0);
|
||||
if (!defender.equals(player)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO this should be a factor but needs some alignment with AttachAi
|
||||
//boolean leavesPlay = !ComputerUtilCard.hasActiveUndyingOrPersist(attacker)
|
||||
// && ((attacker.hasKeyword(Keyword.VANISHING) && attacker.getCounters(CounterEnumType.TIME) == 1)
|
||||
// || (attacker.hasKeyword(Keyword.FADING) && attacker.getCounters(CounterEnumType.FADE) == 0)
|
||||
// || attacker.hasSVar("EndOfTurnLeavePlay"));
|
||||
// The creature won't untap next turn
|
||||
return !atacker.isTapped() || Untap.canUntap(atacker);
|
||||
} // canAttackNextTurn(Card, GameEntity)
|
||||
return !attacker.isTapped() || Untap.canUntap(attacker);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
||||
@@ -337,17 +337,17 @@ public class ComputerUtilCost {
|
||||
}
|
||||
|
||||
public static boolean isSacrificeSelfCost(final Cost cost) {
|
||||
if (cost == null) {
|
||||
return false;
|
||||
}
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (part instanceof CostSacrifice) {
|
||||
if ("CARDNAME".equals(part.getType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
if (cost == null) {
|
||||
return false;
|
||||
}
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (part instanceof CostSacrifice) {
|
||||
if ("CARDNAME".equals(part.getType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -367,13 +367,13 @@ public class ComputerUtilCost {
|
||||
if (part instanceof CostTapType) {
|
||||
/*
|
||||
* Only crew with creatures weaker than vehicle
|
||||
*
|
||||
*
|
||||
* Possible improvements:
|
||||
* - block against evasive (flyers, intimidate, etc.)
|
||||
* - break board stall by racing with evasive vehicle
|
||||
*/
|
||||
if (sa.hasParam("Crew")) {
|
||||
Card vehicle = AnimateAi.becomeAnimated(source, sa);
|
||||
Card vehicle = AnimateAi.becomeAnimated(source, sa);
|
||||
final int vehicleValue = ComputerUtilCard.evaluateCreature(vehicle);
|
||||
String type = part.getType();
|
||||
String totalP = type.split("withTotalPowerGE")[1];
|
||||
@@ -390,7 +390,7 @@ public class ComputerUtilCost {
|
||||
return ComputerUtil.chooseTapTypeAccumulatePower(ai, type, sa, true,
|
||||
Integer.parseInt(totalP), exclude) != null;
|
||||
}
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -478,9 +478,9 @@ public class ComputerUtilCost {
|
||||
}
|
||||
}
|
||||
for (Card c : player.getCardsIn(ZoneType.Command)) {
|
||||
if (cannotBeCountered) {
|
||||
continue;
|
||||
}
|
||||
if (cannotBeCountered) {
|
||||
continue;
|
||||
}
|
||||
final String snem = c.getSVar("SpellsNeedExtraManaEffect");
|
||||
if (!StringUtils.isBlank(snem)) {
|
||||
if (StringUtils.isNumeric(snem)) {
|
||||
@@ -548,7 +548,7 @@ public class ComputerUtilCost {
|
||||
}
|
||||
|
||||
return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded)
|
||||
&& CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa);
|
||||
&& CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa);
|
||||
} // canPayCost()
|
||||
|
||||
public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView<Player> payers) {
|
||||
@@ -609,10 +609,10 @@ public class ComputerUtilCost {
|
||||
return false;
|
||||
}
|
||||
} else if (aiLogic != null && aiLogic.startsWith("LifeLE")) {
|
||||
// if payer can't lose life its no need to pay unless
|
||||
if (!payer.canLoseLife())
|
||||
return false;
|
||||
else if (payer.getLife() <= Integer.valueOf(aiLogic.substring(6))) {
|
||||
// if payer can't lose life its no need to pay unless
|
||||
if (!payer.canLoseLife())
|
||||
return false;
|
||||
else if (payer.getLife() <= AbilityUtils.calculateAmount(source, aiLogic.substring(6), sa)) {
|
||||
return true;
|
||||
}
|
||||
} else if ("WillAttack".equals(aiLogic)) {
|
||||
@@ -638,13 +638,13 @@ public class ComputerUtilCost {
|
||||
// Didn't have any of the data on the original SA to pay dependant costs
|
||||
|
||||
return checkLifeCost(payer, cost, source, 4, sa)
|
||||
&& checkDamageCost(payer, cost, source, 4)
|
||||
&& (isMine || checkSacrificeCost(payer, cost, source, sa))
|
||||
&& (isMine || checkDiscardCost(payer, cost, source, sa))
|
||||
&& (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2)
|
||||
&& (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2)
|
||||
&& (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1)
|
||||
&& (!source.getName().equals("Chain of Vapor") || (payer.getWeakestOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3));
|
||||
&& checkDamageCost(payer, cost, source, 4)
|
||||
&& (isMine || checkSacrificeCost(payer, cost, source, sa))
|
||||
&& (isMine || checkDiscardCost(payer, cost, source, sa))
|
||||
&& (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2)
|
||||
&& (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2)
|
||||
&& (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1)
|
||||
&& (!source.getName().equals("Chain of Vapor") || (payer.getWeakestOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3));
|
||||
}
|
||||
|
||||
public static Set<String> getAvailableManaColors(Player ai, Card additionalLand) {
|
||||
@@ -684,14 +684,14 @@ public class ComputerUtilCost {
|
||||
public static int getMaxXValue(SpellAbility sa, Player ai) {
|
||||
final Card source = sa.getHostCard();
|
||||
final SpellAbility root = sa.getRootAbility();
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Cost abCost = root.getPayCosts();
|
||||
if (abCost == null || !abCost.hasXInAnyCostPart()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Integer val = null;
|
||||
|
||||
if (sa.costHasManaX()) {
|
||||
if (root.costHasManaX()) {
|
||||
val = ComputerUtilMana.determineLeftoverMana(root, ai);
|
||||
}
|
||||
|
||||
|
||||
@@ -341,7 +341,7 @@ public class ComputerUtilMana {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts)) {
|
||||
if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts, cost.getXManaCostPaidByColor())) {
|
||||
return paymentChoice;
|
||||
}
|
||||
}
|
||||
@@ -547,7 +547,7 @@ public class ComputerUtilMana {
|
||||
}
|
||||
|
||||
// get a mana of this type from floating, bail if none available
|
||||
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), (byte) -1);
|
||||
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), (byte) -1, cost.getXManaCostPaidByColor());
|
||||
if (mana != null) {
|
||||
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, false)) {
|
||||
manaSpentToPay.add(0, mana);
|
||||
@@ -936,7 +936,7 @@ public class ComputerUtilMana {
|
||||
}
|
||||
|
||||
// get a mana of this type from floating, bail if none available
|
||||
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), hasConverge ? cost.getColorsPaid() : -1);
|
||||
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), hasConverge ? cost.getColorsPaid() : -1, cost.getXManaCostPaidByColor());
|
||||
if (mana != null) {
|
||||
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, test)) {
|
||||
manaSpentToPay.add(0, mana);
|
||||
@@ -965,8 +965,10 @@ public class ComputerUtilMana {
|
||||
* a {@link forge.game.spellability.SpellAbility} object.
|
||||
* @return a {@link forge.game.mana.Mana} object.
|
||||
*/
|
||||
private static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid) {
|
||||
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard, saBeingPaidFor, restriction, colorsPaid);
|
||||
private static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor,
|
||||
String restriction, final byte colorsPaid, Map<String, Integer> xManaCostPaidByColor) {
|
||||
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard,
|
||||
saBeingPaidFor, restriction, colorsPaid, xManaCostPaidByColor);
|
||||
|
||||
// Exclude border case
|
||||
if (weightedOptions.isEmpty()) {
|
||||
@@ -1015,9 +1017,13 @@ public class ComputerUtilMana {
|
||||
}
|
||||
|
||||
private static List<Pair<Mana, Integer>> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard,
|
||||
final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid) {
|
||||
final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid, Map<String, Integer> xManaCostPaidByColor) {
|
||||
final List<Pair<Mana, Integer>> weightedOptions = new ArrayList<>();
|
||||
for (final Mana thisMana : manapool) {
|
||||
if (shard == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(MagicColor.toShortString(thisMana.getColor()), xManaCostPaidByColor)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
|
||||
continue;
|
||||
}
|
||||
@@ -1093,7 +1099,7 @@ public class ComputerUtilMana {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts) {
|
||||
private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts, Map<String, Integer> xManaCostPaidByColor) {
|
||||
final Card sourceCard = ma.getHostCard();
|
||||
|
||||
if (isManaSourceReserved(ai, sourceCard, sa)) {
|
||||
@@ -1131,6 +1137,10 @@ public class ComputerUtilMana {
|
||||
|
||||
if (m.isComboMana()) {
|
||||
for (String s : m.getComboColors().split(" ")) {
|
||||
if (toPay == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(s, xManaCostPaidByColor)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ("Any".equals(s) || ai.getManaPool().canPayForShardWithColor(toPay, ManaAtom.fromName(s)))
|
||||
return true;
|
||||
}
|
||||
@@ -1141,6 +1151,9 @@ public class ComputerUtilMana {
|
||||
Set<String> reflected = CardUtil.getReflectableManaColors(ma);
|
||||
|
||||
for (byte c : MagicColor.WUBRG) {
|
||||
if (toPay == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(MagicColor.toShortString(c), xManaCostPaidByColor)) {
|
||||
continue;
|
||||
}
|
||||
if (ai.getManaPool().canPayForShardWithColor(toPay, c) && reflected.contains(MagicColor.toLongString(c))) {
|
||||
m.setExpressChoice(MagicColor.toShortString(c));
|
||||
return true;
|
||||
@@ -1148,6 +1161,16 @@ public class ComputerUtilMana {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (toPay == ManaCostShard.COLORED_X) {
|
||||
for (String s : m.mana().split(" ")) {
|
||||
if (ManaCostBeingPaid.canColoredXShardBePaidByColor(s, xManaCostPaidByColor)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1434,17 +1457,26 @@ public class ComputerUtilMana {
|
||||
// Tack xMana Payments into mana here if X is a set value
|
||||
if (cost.getXcounter() > 0 || extraMana > 0) {
|
||||
int manaToAdd = 0;
|
||||
int xCounter = cost.getXcounter();
|
||||
if (test && extraMana > 0) {
|
||||
final int multiplicator = Math.max(cost.getXcounter(), 1);
|
||||
final int multiplicator = Math.max(xCounter, 1);
|
||||
manaToAdd = extraMana * multiplicator;
|
||||
} else {
|
||||
manaToAdd = AbilityUtils.calculateAmount(card, "X", sa) * cost.getXcounter();
|
||||
manaToAdd = AbilityUtils.calculateAmount(card, "X", sa) * xCounter;
|
||||
}
|
||||
|
||||
cost.increaseShard(ManaCostShard.parseNonGeneric(sa.getParamOrDefault("XColor", "1")), manaToAdd);
|
||||
String xColor = sa.getParamOrDefault("XColor", "1");
|
||||
if (card.hasKeyword("Spend only colored mana on X. No more than one mana of each color may be spent this way.")) {
|
||||
xColor = "WUBRGX";
|
||||
}
|
||||
if (xCounter > 0) {
|
||||
cost.setXManaCostPaid(manaToAdd / xCounter, xColor);
|
||||
} else {
|
||||
cost.increaseShard(ManaCostShard.parseNonGeneric(xColor), manaToAdd);
|
||||
}
|
||||
|
||||
if (!test) {
|
||||
sa.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
||||
sa.setXManaCostPaid(manaToAdd / xCounter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1531,7 +1563,7 @@ public class ComputerUtilMana {
|
||||
public boolean apply(final Card c) {
|
||||
for (final SpellAbility am : getAIPlayableMana(c)) {
|
||||
am.setActivatingPlayer(ai);
|
||||
if (!checkPlayable || am.canPlay()) {
|
||||
if (!checkPlayable || (am.canPlay() && am.checkRestrictions(ai))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,12 @@ package forge.ai;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import forge.StaticData;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.MagicColor;
|
||||
@@ -27,7 +16,6 @@ import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.effects.DetachedCardEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCloneStates;
|
||||
@@ -44,9 +32,7 @@ import forge.game.mana.ManaPool;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilityManaPart;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.PlayerZone;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.item.IPaperCard;
|
||||
@@ -770,23 +756,10 @@ public abstract class GameState {
|
||||
}
|
||||
game.fireEvent(new GameEventAttackersDeclared(attackingPlayer, attackersMap));
|
||||
|
||||
if (!combat.getAttackers().isEmpty()) {
|
||||
List<GameEntity> attackedTarget = Lists.newArrayList();
|
||||
for (final Card c : combat.getAttackers()) {
|
||||
attackedTarget.add(combat.getDefenderByAttacker(c));
|
||||
}
|
||||
final Map<AbilityKey, Object> runParams = Maps.newEnumMap(AbilityKey.class);
|
||||
runParams.put(AbilityKey.Attackers, combat.getAttackers());
|
||||
runParams.put(AbilityKey.AttackingPlayer, combat.getAttackingPlayer());
|
||||
runParams.put(AbilityKey.AttackedTarget, attackedTarget);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.AttackersDeclared, runParams, false);
|
||||
}
|
||||
|
||||
for (final Card c : combat.getAttackers()) {
|
||||
CombatUtil.checkDeclaredAttacker(game, c, combat);
|
||||
CombatUtil.checkDeclaredAttacker(game, c, combat, false);
|
||||
}
|
||||
|
||||
game.getTriggerHandler().resetActiveTriggers();
|
||||
game.updateCombatForView();
|
||||
game.fireEvent(new GameEventCombatChanged());
|
||||
|
||||
@@ -1346,7 +1319,7 @@ public abstract class GameState {
|
||||
}
|
||||
else if (info.startsWith("OnAdventure")) {
|
||||
String abAdventure = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell";
|
||||
AbilitySub saAdventure = (AbilitySub)AbilityFactory.getAbility(abAdventure, c);
|
||||
SpellAbility saAdventure = AbilityFactory.getAbility(abAdventure, c);
|
||||
StringBuilder sbPlay = new StringBuilder();
|
||||
sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure");
|
||||
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");
|
||||
|
||||
@@ -530,7 +530,14 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
@Override
|
||||
public CardCollectionView chooseCardsToDiscardUnlessType(int num, CardCollectionView hand, String uType, SpellAbility sa) {
|
||||
final CardCollectionView cardsOfType = CardLists.getType(hand, uType);
|
||||
String [] splitUTypes = uType.split(",");
|
||||
CardCollection cardsOfType = new CardCollection();
|
||||
for (String part : splitUTypes) {
|
||||
CardCollection partCards = CardLists.getType(hand, part);
|
||||
if (!partCards.isEmpty()) {
|
||||
cardsOfType.addAll(partCards);
|
||||
}
|
||||
}
|
||||
if (!cardsOfType.isEmpty()) {
|
||||
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
|
||||
return new CardCollection(toDiscard);
|
||||
@@ -1022,7 +1029,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
*/
|
||||
if (sa.isMayChooseNewTargets() && !sa.setupTargets()) {
|
||||
if (sa.isSpell()) {
|
||||
sa.getHostCard().ceaseToExist();
|
||||
player.getGame().getAction().ceaseToExist(sa.getHostCard(), false);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -1056,14 +1063,13 @@ public class PlayerControllerAi extends PlayerController {
|
||||
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
|
||||
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
||||
Spell spell = (Spell) tgtSA;
|
||||
if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) {
|
||||
if (tgtSA.checkRestrictions(brains.getPlayer()) && (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional)) {
|
||||
if (noManaCost) {
|
||||
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame());
|
||||
} else {
|
||||
return ComputerUtil.playStack(tgtSA, player, getGame());
|
||||
}
|
||||
} else
|
||||
return false; // didn't play spell
|
||||
return ComputerUtil.playStack(tgtSA, player, getGame());
|
||||
}
|
||||
return false; // didn't play spell
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1086,7 +1092,6 @@ public class PlayerControllerAi extends PlayerController {
|
||||
boolean allCreatures = Iterables.all(Iterables.concat(pile1, pile2), CardPredicates.Presets.CREATURES);
|
||||
int cmc1 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile1) : ComputerUtilCard.evaluatePermanentList(pile1);
|
||||
int cmc2 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile2) : ComputerUtilCard.evaluatePermanentList(pile2);
|
||||
System.out.println("value:" + cmc1 + " " + cmc2);
|
||||
|
||||
// for now, this assumes that the outcome will be bad
|
||||
// TODO: This should really have a ChooseLogic param to
|
||||
|
||||
@@ -112,6 +112,63 @@ public class SpecialCardAi {
|
||||
}
|
||||
}
|
||||
|
||||
// Brain in a Jar
|
||||
public static class BrainInAJar {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
int counterNum = source.getCounters(CounterEnumType.CHARGE);
|
||||
// no need for logic
|
||||
if (counterNum == 0) {
|
||||
return false;
|
||||
}
|
||||
int libsize = ai.getCardsIn(ZoneType.Library).size();
|
||||
|
||||
final CardCollection hand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.or(
|
||||
CardPredicates.isType("Instant"), CardPredicates.isType("Sorcery")));
|
||||
if (!hand.isEmpty()) {
|
||||
// has spell that can be cast in hand with put ability
|
||||
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum + 1)).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// has spell that can be cast if one counter is removed
|
||||
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum)).isEmpty()) {
|
||||
sa.setXManaCostPaid(1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
final CardCollection library = CardLists.filter(ai.getCardsIn(ZoneType.Library), Predicates.or(
|
||||
CardPredicates.isType("Instant"), CardPredicates.isType("Sorcery")));
|
||||
if (!library.isEmpty()) {
|
||||
// get max cmc of instant or sorceries in the libary
|
||||
int maxCMC = 0;
|
||||
for (final Card c : library) {
|
||||
int v = c.getCMC();
|
||||
if (c.isSplitCard()) {
|
||||
v = Math.max(c.getCMC(Card.SplitCMCMode.LeftSplitCMC), c.getCMC(Card.SplitCMCMode.RightSplitCMC));
|
||||
}
|
||||
if (v > maxCMC) {
|
||||
maxCMC = v;
|
||||
}
|
||||
}
|
||||
// there is a spell with more CMC, no need to remove counter
|
||||
if (counterNum + 1 < maxCMC) {
|
||||
return false;
|
||||
}
|
||||
int maxToRemove = counterNum - maxCMC + 1;
|
||||
// no Scry 0, even if its catched from later stuff
|
||||
if (maxToRemove <= 0) {
|
||||
return false;
|
||||
}
|
||||
sa.setXManaCostPaid(maxToRemove);
|
||||
} else {
|
||||
// no Instant or Sorceries anymore, just scry
|
||||
sa.setXManaCostPaid(Math.min(counterNum, libsize));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Chain of Acid
|
||||
public static class ChainOfAcid {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
@@ -159,8 +216,7 @@ public class SpecialCardAi {
|
||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
final Combat combat = ai.getGame().getCombat();
|
||||
|
||||
Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa);
|
||||
animated.addType("Creature");
|
||||
Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa.getSubAbility());
|
||||
if (sa.getHostCard().canReceiveCounters(CounterEnumType.P1P1)) {
|
||||
animated.addCounter(CounterEnumType.P1P1, 2, ai, false, null);
|
||||
}
|
||||
@@ -170,10 +226,6 @@ public class SpecialCardAi {
|
||||
|
||||
return isOppEOT || isValuableAttacker || isValuableBlocker;
|
||||
}
|
||||
|
||||
public static SpellAbility considerAnimating(final Player ai, final SpellAbility sa, final List<SpellAbility> options) {
|
||||
return ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) ? options.get(0) : options.get(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Cursed Scroll
|
||||
@@ -911,6 +963,39 @@ public class SpecialCardAi {
|
||||
}
|
||||
}
|
||||
|
||||
// Multiple Choice
|
||||
public static class MultipleChoice {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
int maxX = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
|
||||
if (maxX == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean canScryDraw = maxX >= 1 && ai.getCardsIn(ZoneType.Library).size() >= 3; // TODO: generalize / use profile values
|
||||
boolean canBounce = maxX >= 2 && !ai.getOpponents().getCreaturesInPlay().isEmpty();
|
||||
boolean shouldBounce = canBounce && ComputerUtilCard.evaluateCreature(ComputerUtilCard.getWorstCreatureAI(ai.getOpponents().getCreaturesInPlay())) > 210; // 180 is the level of a 4/4 token creature
|
||||
boolean canMakeToken = maxX >= 3;
|
||||
boolean canDoAll = maxX >= 4 && canScryDraw && shouldBounce;
|
||||
|
||||
if (canDoAll) {
|
||||
sa.setXManaCostPaid(4);
|
||||
return true;
|
||||
} else if (canMakeToken) {
|
||||
sa.setXManaCostPaid(3);
|
||||
return true;
|
||||
} else if (shouldBounce) {
|
||||
sa.setXManaCostPaid(2);
|
||||
return true;
|
||||
} else if (canScryDraw) {
|
||||
sa.setXManaCostPaid(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Necropotence
|
||||
public static class Necropotence {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
|
||||
@@ -58,6 +58,11 @@ public abstract class SpellAbilityAi {
|
||||
final Card source = sa.getHostCard();
|
||||
final Cost cost = sa.getPayCosts();
|
||||
|
||||
if (sa.hasParam("AICheckCanPlayWithDefinedX")) {
|
||||
// FIXME: can this somehow be simplified without the need for an extra AI hint?
|
||||
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
|
||||
}
|
||||
|
||||
if (!checkConditions(ai, sa, sa.getConditions())) {
|
||||
SpellAbility sub = sa.getSubAbility();
|
||||
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {
|
||||
@@ -299,7 +304,7 @@ public abstract class SpellAbilityAi {
|
||||
final AbilitySub subAb = ab.getSubAbility();
|
||||
return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
|
||||
}
|
||||
|
||||
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return true;
|
||||
|
||||
@@ -34,6 +34,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.BidLife, BidLifeAi.class)
|
||||
.put(ApiType.Bond, BondAi.class)
|
||||
.put(ApiType.Branch, AlwaysPlayAi.class)
|
||||
.put(ApiType.Camouflage, ChooseCardAi.class)
|
||||
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
|
||||
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
||||
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
||||
@@ -94,6 +95,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.Haunt, HauntAi.class)
|
||||
.put(ApiType.ImmediateTrigger, ImmediateTriggerAi.class)
|
||||
.put(ApiType.Investigate, InvestigateAi.class)
|
||||
.put(ApiType.Learn, LearnAi.class)
|
||||
.put(ApiType.LoseLife, LifeLoseAi.class)
|
||||
.put(ApiType.LosesGame, GameLossAi.class)
|
||||
.put(ApiType.Mana, ManaEffectAi.class)
|
||||
|
||||
@@ -21,8 +21,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
final Player opp = ai.getStrongestOpponent();
|
||||
|
||||
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
||||
if (list.isEmpty()) {
|
||||
@@ -40,12 +39,13 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
||||
sa.getTargets().add(opp);
|
||||
}
|
||||
|
||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = ai.getStrongestOpponent();
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
@@ -30,6 +30,7 @@ import forge.game.cost.CostPutCounter;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.staticability.StaticAbilityContinuous;
|
||||
@@ -243,6 +244,11 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return player.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2);
|
||||
}
|
||||
|
||||
private boolean animateTgtAI(final SpellAbility sa) {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -1129,8 +1130,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
for (Card target : list) {
|
||||
for (Trigger t : target.getTriggers()) {
|
||||
if (t.getMode() == TriggerType.SpellCast) {
|
||||
final Map<String, String> params = t.getMapParams();
|
||||
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))) {
|
||||
if ("Card.Self".equals(t.getParam("TargetsValid")) && "You".equals(t.getParam("ValidActivatingPlayer"))) {
|
||||
magnetList.add(target);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -13,18 +13,21 @@ public class BalanceAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
String logic = sa.getParam("AILogic");
|
||||
|
||||
int diff = 0;
|
||||
// TODO Add support for multiplayer logic
|
||||
final Player opp = aiPlayer.getWeakestOpponent();
|
||||
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
||||
Player opp = aiPlayer.getWeakestOpponent();
|
||||
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||
for (Player min : aiPlayer.getOpponents()) {
|
||||
if (min.getCardsIn(ZoneType.Battlefield).size() < opp.getCardsIn(ZoneType.Battlefield).size()) {
|
||||
opp = min;
|
||||
}
|
||||
}
|
||||
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
if ("BalanceCreaturesAndLands".equals(logic)) {
|
||||
// Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting
|
||||
// TODO Copied over from hardcoded Balance. We should be checking value of the lands/creatures for each opponent, not just counting
|
||||
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
|
||||
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
|
||||
diff += 1.5 * ( CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
|
||||
diff += 1.5 * (CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
|
||||
CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size());
|
||||
}
|
||||
else if ("BalancePermanents".equals(logic)) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
@@ -24,7 +25,7 @@ public class BidLifeAi extends SpellAbilityAi {
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canTgtCreature()) {
|
||||
List<Card> list = CardLists.getTargetableCards(aiPlayer.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), sa);
|
||||
List<Card> list = CardLists.getTargetableCards(AiAttackController.choosePreferredDefenderPlayer(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
|
||||
@@ -10,6 +10,7 @@ import forge.game.card.Card;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetChoices;
|
||||
|
||||
public class ChangeTargetsAi extends SpellAbilityAi {
|
||||
|
||||
@@ -40,18 +41,20 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
||||
// nothing on stack, so nothing to target
|
||||
return false;
|
||||
}
|
||||
final TargetChoices topTargets = topSa.getTargets();
|
||||
final Card topHost = topSa.getHostCard();
|
||||
|
||||
if (sa.getTargets().size() != 0) {
|
||||
if (sa.getTargets().size() != 0 && sa.isTrigger()) {
|
||||
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!topSa.usesTargeting() || topSa.getTargets().getTargetCards().contains(sa.getHostCard())) {
|
||||
if (!topSa.usesTargeting() || topTargets.getTargetCards().contains(sa.getHostCard())) {
|
||||
// if this does not target at all or already targets host, no need to redirect it again
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Card tgt : topSa.getTargets().getTargetCards()) {
|
||||
for (Card tgt : topTargets.getTargetCards()) {
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) {
|
||||
// We are already targeting at least one card with the same name (e.g. in presence of 2+ Spellskites),
|
||||
// no need to retarget again to another one
|
||||
@@ -59,7 +62,7 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (topSa.getHostCard() != null && !topSa.getHostCard().getController().isOpponentOf(aiPlayer)) {
|
||||
if (topHost != null && !topHost.getController().isOpponentOf(aiPlayer)) {
|
||||
// make sure not to redirect our own abilities
|
||||
return false;
|
||||
}
|
||||
@@ -71,7 +74,7 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
||||
// don't try retargeting a spell that the current card can't legally retarget (e.g. Muck Drubb + Lightning Bolt to the face)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (sa.getPayCosts().getCostMana() != null && sa.getPayCosts().getCostMana().getMana().hasPhyrexian()) {
|
||||
ManaCost manaCost = sa.getPayCosts().getCostMana().getMana();
|
||||
int payDamage = manaCost.getPhyrexianCount() * 2;
|
||||
@@ -80,12 +83,22 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
||||
ManaCost normalizedMana = manaCost.getNormalizedMana();
|
||||
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
|
||||
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
|
||||
&& topSa.getTargets().contains(aiPlayer)) {
|
||||
&& topTargets.contains(aiPlayer)) {
|
||||
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Card firstCard = topTargets.getFirstTargetedCard();
|
||||
// if we're not the target don't intervene unless we can steal a buff
|
||||
if (firstCard != null && !aiPlayer.equals(firstCard.getController()) && !topHost.getController().equals(firstCard.getController()) && !topHost.getController().getAllies().contains(firstCard.getController())) {
|
||||
return false;
|
||||
}
|
||||
Player firstPlayer = topTargets.getFirstTargetedPlayer();
|
||||
if (firstPlayer != null && !aiPlayer.equals(firstPlayer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(topSa);
|
||||
return true;
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.AiBlockController;
|
||||
import forge.ai.AiCardMemory;
|
||||
import forge.ai.AiController;
|
||||
@@ -57,6 +58,7 @@ import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbilityMustTarget;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
@@ -263,7 +265,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
ZoneType origin = null;
|
||||
final Player opponent = ai.getWeakestOpponent();
|
||||
final Player opponent = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||
|
||||
if (sa.hasParam("Origin")) {
|
||||
@@ -471,7 +473,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
// if putting cards from hand to library and parent is drawing cards
|
||||
// make sure this will actually do something:
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Player opp = aiPlayer.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||
if (tgt != null && tgt.canTgtPlayer()) {
|
||||
boolean isCurse = sa.isCurse();
|
||||
if (isCurse && sa.canTarget(opp)) {
|
||||
@@ -530,7 +532,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
Iterable<Player> pDefined;
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if ((tgt != null) && tgt.canTgtPlayer()) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (sa.isCurse()) {
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
@@ -892,7 +894,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
||||
sa.setXManaCostPaid(xPay);
|
||||
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
|
||||
}
|
||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
||||
|
||||
@@ -913,9 +914,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
if (sa.isSpell()) {
|
||||
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
||||
}
|
||||
//System.out.println("isPreferredTarget " + list);
|
||||
if (sa.hasParam("AttachedTo")) {
|
||||
//System.out.println("isPreferredTarget att " + list);
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
@@ -927,7 +926,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
//System.out.println("isPreferredTarget ok " + list);
|
||||
}
|
||||
|
||||
if (list.size() < sa.getMinTargets()) {
|
||||
@@ -1163,6 +1161,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
if (!list.isEmpty()) {
|
||||
if (destination.equals(ZoneType.Battlefield) || origin.contains(ZoneType.Battlefield)) {
|
||||
// filter by MustTarget requirement
|
||||
CardCollection originalList = new CardCollection(list);
|
||||
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
|
||||
|
||||
final Card mostExpensive = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false);
|
||||
if (mostExpensive.isCreature()) {
|
||||
// if a creature is most expensive take the best one
|
||||
@@ -1191,6 +1193,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore original list for next loop if filtered by MustTarget requirement
|
||||
if (mustTargetFiltered) {
|
||||
list = originalList;
|
||||
}
|
||||
} else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) {
|
||||
List<Card> nonLands = CardLists.getNotType(list, "Land");
|
||||
// Prefer to pull a creature, generally more useful for AI.
|
||||
@@ -1482,9 +1489,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
if ("DeathgorgeScavenger".equals(logic)) {
|
||||
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
|
||||
} else if ("ExtraplanarLens".equals(logic)) {
|
||||
}
|
||||
if ("ExtraplanarLens".equals(logic)) {
|
||||
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
|
||||
} else if ("ExileCombatThreat".equals(logic)) {
|
||||
}
|
||||
if ("ExileCombatThreat".equals(logic)) {
|
||||
return doExileCombatThreatLogic(ai, sa);
|
||||
}
|
||||
|
||||
@@ -1984,11 +1993,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||
}
|
||||
|
||||
if (toPay == 0) {
|
||||
canBeSaved.add(potentialTgt);
|
||||
}
|
||||
|
||||
if (toPay <= usableManaSources) {
|
||||
if (toPay == 0 || toPay <= usableManaSources) {
|
||||
canBeSaved.add(potentialTgt);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
@@ -74,10 +75,8 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
return !choices.isEmpty();
|
||||
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
|
||||
return choices.size() >= 2;
|
||||
} else if (aiLogic.equals("Clone") || aiLogic.equals("Vesuva")) {
|
||||
final String filter = aiLogic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
||||
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
||||
|
||||
} else if (aiLogic.equals("Clone")) {
|
||||
final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
|
||||
choices = CardLists.getValidCards(choices, filter, host.getController(), host, sa);
|
||||
return !choices.isEmpty();
|
||||
} else if (aiLogic.equals("Never")) {
|
||||
@@ -114,7 +113,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host, sa).isEmpty();
|
||||
} else if (aiLogic.equals("Duneblast")) {
|
||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||
CardCollection oppCreatures = ai.getWeakestOpponent().getCreaturesInPlay();
|
||||
CardCollection oppCreatures = AiAttackController.choosePreferredDefenderPlayer(ai).getCreaturesInPlay();
|
||||
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
|
||||
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
|
||||
|
||||
@@ -172,18 +171,13 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
options = CardLists.filter(options, Presets.UNTAPPED);
|
||||
}
|
||||
choice = ComputerUtilCard.getBestCreatureAI(options);
|
||||
} else if (logic.equals("Clone") || logic.equals("Vesuva")) {
|
||||
final String filter = logic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary" :
|
||||
"Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
||||
|
||||
} else if (logic.equals("Clone")) {
|
||||
final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
|
||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||
if (!newOptions.isEmpty()) {
|
||||
options = newOptions;
|
||||
}
|
||||
choice = ComputerUtilCard.getBestAI(options);
|
||||
if (logic.equals("Vesuva") && "Vesuva".equals(choice.getName())) {
|
||||
choice = null;
|
||||
}
|
||||
} else if ("RandomNonLand".equals(logic)) {
|
||||
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host, sa);
|
||||
choice = Aggregates.random(options);
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.StaticData;
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpecialCardAi;
|
||||
@@ -34,9 +35,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
String logic = sa.getParam("AILogic");
|
||||
if (logic.equals("MomirAvatar")) {
|
||||
return SpecialCardAi.MomirVigAvatar.consider(ai, sa);
|
||||
} else if (logic.equals("CursedScroll")) {
|
||||
if (logic.equals("CursedScroll")) {
|
||||
return SpecialCardAi.CursedScroll.consider(ai, sa);
|
||||
}
|
||||
|
||||
@@ -44,7 +43,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
sa.getTargets().add(ai.getWeakestOpponent());
|
||||
sa.getTargets().add(AiAttackController.choosePreferredDefenderPlayer(ai));
|
||||
} else {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -16,7 +17,7 @@ public class ChooseEvenOddAi extends SpellAbilityAi {
|
||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
Player opp = aiPlayer.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
|
||||
@@ -158,6 +158,22 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
return others;
|
||||
} else if ("Counters".equals(logic)) {
|
||||
// TODO: this code will need generalization if this logic is used for cards other
|
||||
// than Elspeth Conquers Death with different choice parameters
|
||||
SpellAbility p1p1 = null, loyalty = null;
|
||||
for (final SpellAbility sp : spells) {
|
||||
if (("P1P1").equals(sp.getParam("CounterType"))) {
|
||||
p1p1 = sp;
|
||||
} else {
|
||||
loyalty = sp;
|
||||
}
|
||||
}
|
||||
if (sa.getParent().getTargetCard() != null && sa.getParent().getTargetCard().getType().isPlaneswalker()) {
|
||||
return loyalty;
|
||||
} else {
|
||||
return p1p1;
|
||||
}
|
||||
} else if ("Fatespinner".equals(logic)) {
|
||||
SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null;
|
||||
for (final SpellAbility sp : spells) {
|
||||
@@ -363,8 +379,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
} else if ("Riot".equals(logic)) {
|
||||
SpellAbility counterSA = spells.get(0), hasteSA = spells.get(1);
|
||||
return preferHasteForRiot(sa, player) ? hasteSA : counterSA;
|
||||
} else if ("CrawlingBarrens".equals(logic)) {
|
||||
return SpecialCardAi.CrawlingBarrens.considerAnimating(player, sa, spells);
|
||||
}
|
||||
return spells.get(0); // return first choice if no logic found
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -16,7 +17,7 @@ public class ChooseNumberAi extends SpellAbilityAi {
|
||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
Player opp = aiPlayer.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -54,7 +55,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
|
||||
@@ -3,13 +3,17 @@ package forge.ai.ability;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
@@ -95,11 +99,18 @@ public class CloneAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
Card host = sa.getHostCard();
|
||||
boolean chance = true;
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
chance = cloneTgtAI(sa);
|
||||
} else {
|
||||
if (sa.hasParam("Choices")) {
|
||||
CardCollectionView choices = CardLists.getValidCards(host.getGame().getCardsIn(ZoneType.Battlefield),
|
||||
sa.getParam("Choices"), host.getController(), host, sa);
|
||||
|
||||
chance = !choices.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
// Improve AI for triggers. If source is a creature with:
|
||||
@@ -171,18 +182,18 @@ public class CloneAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||
Player targetedPlayer, Map<String, Object> params) {
|
||||
|
||||
final Card host = sa.getHostCard();
|
||||
final String name = host.getName();
|
||||
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 boolean isVesuva = "Vesuva".equals(name) || "Sculpting Steel".equals(name);
|
||||
final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary"));
|
||||
|
||||
String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
||||
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
||||
: "Permanent.YouDontCtrl+notnamed" + name + ",Permanent.nonLegendary+notnamed" + name;
|
||||
|
||||
// TODO: rewrite this block so that this is done somehow more elegantly
|
||||
if (canCloneLegendary) {
|
||||
@@ -201,12 +212,13 @@ public class CloneAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options);
|
||||
|
||||
if (isVesuva && "Vesuva".equals(choice.getName())) {
|
||||
choice = null;
|
||||
// prevent loop of choosing copy of same card
|
||||
if (isVesuva) {
|
||||
options = CardLists.filter(options, Predicates.not(CardPredicates.sharesNameWith(host)));
|
||||
}
|
||||
|
||||
Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options);
|
||||
|
||||
return choice;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.ai.ability;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
@@ -29,7 +30,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
||||
sa.resetTargets();
|
||||
|
||||
CardCollection list =
|
||||
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
CardLists.getValidCards(AiAttackController.choosePreferredDefenderPlayer(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
// AI won't try to grab cards that are filtered out of AI decks on
|
||||
// purpose
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
|
||||
@@ -39,6 +39,7 @@ import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbilityMustTarget;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
@@ -211,6 +212,10 @@ public class ControlGainAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
while (t == null) {
|
||||
// filter by MustTarget requirement
|
||||
CardCollection originalList = new CardCollection(list);
|
||||
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
|
||||
|
||||
if (planeswalkers > 0) {
|
||||
t = ComputerUtilCard.getBestPlaneswalkerAI(list);
|
||||
} else if (creatures > 0) {
|
||||
@@ -238,6 +243,11 @@ public class ControlGainAi extends SpellAbilityAi {
|
||||
enchantments--;
|
||||
}
|
||||
|
||||
// Restore original list for next loop if filtered by MustTarget requirement
|
||||
if (mustTargetFiltered) {
|
||||
list = originalList;
|
||||
}
|
||||
|
||||
if (!sa.canTarget(t)) {
|
||||
list.remove(t);
|
||||
t = null;
|
||||
|
||||
@@ -44,7 +44,9 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("MimicVat".equals(aiLogic)) {
|
||||
if ("MomirAvatar".equals(aiLogic)) {
|
||||
return SpecialCardAi.MomirVigAvatar.consider(aiPlayer, sa);
|
||||
} else if ("MimicVat".equals(aiLogic)) {
|
||||
return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
|
||||
} else if ("AtEOT".equals(aiLogic)) {
|
||||
return ph.is(PhaseType.END_OF_TURN);
|
||||
|
||||
@@ -372,54 +372,57 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
|
||||
if (!aiList.isEmpty()) {
|
||||
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
|
||||
Card lki = CardUtil.getLKICopy(src);
|
||||
lki.clearCounters();
|
||||
// go for opponent when value implies debuff
|
||||
if (ComputerUtilCard.evaluateCreature(src) > ComputerUtilCard.evaluateCreature(lki)) {
|
||||
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
|
||||
if (!aiList.isEmpty()) {
|
||||
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
|
||||
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
// gain from useless
|
||||
if (ComputerUtilCard.isUselessCreature(ai, card)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// source would leave the game
|
||||
if (card.hasSVar("EndOfTurnLeavePlay")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cType != null) {
|
||||
if (cType.is(CounterEnumType.P1P1) && card.hasKeyword(Keyword.UNDYING)) {
|
||||
return false;
|
||||
}
|
||||
if (cType.is(CounterEnumType.M1M1) && card.hasKeyword(Keyword.PERSIST)) {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
// gain from useless
|
||||
if (ComputerUtilCard.isUselessCreature(ai, card)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!card.canReceiveCounters(cType)) {
|
||||
// source would leave the game
|
||||
if (card.hasSVar("EndOfTurnLeavePlay")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cType != null) {
|
||||
if (cType.is(CounterEnumType.P1P1) && card.hasKeyword(Keyword.UNDYING)) {
|
||||
return false;
|
||||
}
|
||||
if (cType.is(CounterEnumType.M1M1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!card.canReceiveCounters(cType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (best.isEmpty()) {
|
||||
best = aiList;
|
||||
}
|
||||
|
||||
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
||||
|
||||
if (card != null) {
|
||||
sa.getTargets().add(card);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (best.isEmpty()) {
|
||||
best = aiList;
|
||||
}
|
||||
|
||||
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
||||
|
||||
if (card != null) {
|
||||
sa.getTargets().add(card);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// move counter to opponents creature but only if you can not steal
|
||||
// them
|
||||
// try to move to something useless or something that would leave
|
||||
// play
|
||||
// move counter to opponents creature but only if you can not steal them
|
||||
// try to move to something useless or something that would leave play
|
||||
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
|
||||
if (!oppList.isEmpty()) {
|
||||
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
|
||||
@@ -441,7 +444,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
});
|
||||
|
||||
if (best.isEmpty()) {
|
||||
best = aiList;
|
||||
best = oppList;
|
||||
}
|
||||
|
||||
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
||||
@@ -455,7 +458,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
// used for multiple sources -> defied
|
||||
// used for multiple sources -> defined
|
||||
// or for source -> multiple defined
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||
|
||||
@@ -8,8 +8,10 @@ import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.card.Card;
|
||||
@@ -121,6 +123,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
||||
|
||||
final CounterType poison = CounterType.get(CounterEnumType.POISON);
|
||||
|
||||
boolean aggroAI = (((PlayerControllerAi) ai.getController()).getAi()).getBooleanProperty(AiProps.PLAY_AGGRO);
|
||||
// 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)) {
|
||||
@@ -128,7 +131,8 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
||||
return (T)p;
|
||||
}
|
||||
} else {
|
||||
if (p.getCounters(poison) <= 5 || p.canReceiveCounters(poison)) {
|
||||
// poison is risky, should not proliferate them in most cases
|
||||
if ((p.getCounters(poison) <= 5 && aggroAI && p.getCounters(CounterEnumType.EXPERIENCE) + p.getCounters(CounterEnumType.ENERGY) >= 1) || !p.canReceiveCounters(poison)) {
|
||||
return (T)p;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
@@ -313,7 +315,12 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
} else if (logic.startsWith("MoveCounter")) {
|
||||
return doMoveCounterLogic(ai, sa, ph);
|
||||
} else if (logic.equals("CrawlingBarrens")) {
|
||||
return SpecialCardAi.CrawlingBarrens.consider(ai, sa);
|
||||
boolean willActivate = SpecialCardAi.CrawlingBarrens.consider(ai, sa);
|
||||
if (willActivate && ph.getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
// don't use this for mana until after combat
|
||||
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
|
||||
}
|
||||
return willActivate;
|
||||
}
|
||||
|
||||
if (!sa.metConditions() && sa.getSubAbility() == null) {
|
||||
@@ -401,19 +408,37 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if ("Polukranos".equals(logic)) {
|
||||
boolean found = false;
|
||||
for (Trigger tr : source.getTriggers()) {
|
||||
if (!tr.getMode().equals(TriggerType.BecomeMonstrous)) {
|
||||
continue;
|
||||
}
|
||||
SpellAbility oa = tr.ensureAbility();
|
||||
if (oa == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CardCollection targets = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
|
||||
// need to set Activating player
|
||||
oa.setActivatingPlayer(ai);
|
||||
CardCollection targets = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), oa);
|
||||
|
||||
if (!targets.isEmpty()){
|
||||
boolean canSurvive = false;
|
||||
for (Card humanCreature : targets) {
|
||||
if (!FightAi.canKill(humanCreature, source, 0)){
|
||||
canSurvive = true;
|
||||
if (!targets.isEmpty()){
|
||||
boolean canSurvive = false;
|
||||
for (Card humanCreature : targets) {
|
||||
if (!FightAi.canKill(humanCreature, source, 0)){
|
||||
canSurvive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!canSurvive){
|
||||
return false;
|
||||
}
|
||||
if (!canSurvive){
|
||||
return false;
|
||||
}
|
||||
};
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerDamageDone;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
@@ -56,7 +56,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
boolean dmgByCardsInHand = false;
|
||||
|
||||
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&
|
||||
sa.getHostCard().getSVar(sa.getParam("NumDmg")).equals("TargetedPlayer$CardsInHand")) {
|
||||
sa.getSVar(sa.getParam("NumDmg")).equals("TargetedPlayer$CardsInHand")) {
|
||||
dmgByCardsInHand = true;
|
||||
}
|
||||
// Not sure if type choice implemented for the AI yet but it should at least recognize this spell hits harder on larger enemy hand size
|
||||
@@ -75,7 +75,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
||||
// If has triggered ability on dealing damage to an opponent, go for it!
|
||||
Card hostcard = sa.getHostCard();
|
||||
for (Trigger trig : hostcard.getTriggers()) {
|
||||
if (trig instanceof TriggerDamageDone) {
|
||||
if (trig.getMode() == TriggerType.DamageDone) {
|
||||
if (("Opponent".equals(trig.getParam("ValidTarget")))
|
||||
&& (!"True".equals(trig.getParam("CombatDamage")))) {
|
||||
return true;
|
||||
|
||||
@@ -46,6 +46,7 @@ import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetChoices;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbilityMustTarget;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.MyRandom;
|
||||
@@ -343,6 +344,9 @@ public class DamageDealAi extends DamageAiBase {
|
||||
final Game game = source.getGame();
|
||||
List<Card> hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game);
|
||||
|
||||
// Filter MustTarget requirements
|
||||
StaticAbilityMustTarget.filterMustTargetCards(ai, hPlay, sa);
|
||||
|
||||
CardCollection killables = CardLists.filter(hPlay, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
@@ -26,9 +27,6 @@ import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class DebuffAi extends SpellAbilityAi {
|
||||
// *************************************************************************
|
||||
// ***************************** Debuff ************************************
|
||||
// *************************************************************************
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||
@@ -140,7 +138,6 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
|
||||
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||
Card t = null;
|
||||
// boolean goodt = false;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) {
|
||||
@@ -176,19 +173,18 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
* @return a CardCollection.
|
||||
*/
|
||||
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
|
||||
if (!list.isEmpty()) {
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.hasAnyKeyword(kws); // don't add duplicate negative
|
||||
// keywords
|
||||
return c.hasAnyKeyword(kws); // don't add duplicate negative keywords
|
||||
}
|
||||
});
|
||||
}
|
||||
return list;
|
||||
} // getCurseCreatures()
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -216,7 +212,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
list.remove(c);
|
||||
}
|
||||
|
||||
final CardCollection pref = CardLists.filterControlledBy(list, ai.getWeakestOpponent());
|
||||
final CardCollection pref = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||
final CardCollection forced = CardLists.filterControlledBy(list, ai);
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
@@ -242,8 +238,7 @@ public class DebuffAi extends SpellAbilityAi {
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO - if forced targeting, just pick something without the given
|
||||
// keyword
|
||||
// TODO - if forced targeting, just pick something without the given keyword
|
||||
Card c;
|
||||
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
||||
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
||||
|
||||
@@ -2,15 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpecialAiLogic;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
@@ -26,6 +18,7 @@ import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbilityMustTarget;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class DestroyAi extends SpellAbilityAi {
|
||||
@@ -122,17 +115,21 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
|
||||
CardCollection list;
|
||||
|
||||
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Targeting
|
||||
if (sa.usesTargeting()) {
|
||||
// If there's X in payment costs and it's tied to targeting, make sure we set the XManaCostPaid first
|
||||
// (e.g. Heliod's Intervention)
|
||||
if ("X".equals(sa.getTargetRestrictions().getMinTargets()) && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
sa.getRootAbility().setXManaCostPaid(xPay);
|
||||
}
|
||||
|
||||
// Assume there where already enough targets chosen by AI Logic Above
|
||||
if (!sa.canAddMoreTarget() && sa.isTargetNumberValid()) {
|
||||
if (sa.hasParam("AILogic") && !sa.canAddMoreTarget() && sa.isTargetNumberValid()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -140,11 +137,11 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
sa.resetTargets();
|
||||
int maxTargets;
|
||||
|
||||
if (sa.costHasManaX()) {
|
||||
if (sa.getRootAbility().costHasManaX()) {
|
||||
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
|
||||
maxTargets = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||
// need to set XPaid to get the right number for
|
||||
sa.setXManaCostPaid(maxTargets);
|
||||
sa.getRootAbility().setXManaCostPaid(maxTargets);
|
||||
// need to check for maxTargets
|
||||
maxTargets = Math.min(maxTargets, sa.getMaxTargets());
|
||||
} else {
|
||||
@@ -226,6 +223,10 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
// target loop
|
||||
// TODO use can add more Targets
|
||||
while (sa.getTargets().size() < maxTargets) {
|
||||
// filter by MustTarget requirement
|
||||
CardCollection originalList = new CardCollection(list);
|
||||
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
|
||||
|
||||
if (list.isEmpty()) {
|
||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||
sa.resetTargets();
|
||||
@@ -286,6 +287,12 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore original list for next loop if filtered by MustTarget requirement
|
||||
if (mustTargetFiltered) {
|
||||
list = originalList;
|
||||
}
|
||||
|
||||
list.remove(choice);
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
|
||||
@@ -70,12 +70,12 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
return doMassRemovalLogic(ai, sa);
|
||||
}
|
||||
|
||||
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
|
||||
public static boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
Player opponent = ai.getWeakestOpponent(); // TODO: how should this AI logic work for multiplayer and getOpponents()?
|
||||
|
||||
final int CREATURE_EVAL_THRESHOLD = 200;
|
||||
// if we hit the whole board, the other opponents who are not the reason to cast this probably still suffer a bit too
|
||||
final int CREATURE_EVAL_THRESHOLD = 200 / (!sa.usesTargeting() ? ai.getOpponents().size() : 1);
|
||||
|
||||
if (logic.equals("Always")) {
|
||||
return true; // e.g. Tetzimoc, Primal Death, where we want to cast the permanent even if the removal trigger does nothing
|
||||
@@ -93,99 +93,101 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
valid = valid.replace("X", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield),
|
||||
valid.split(","), source.getController(), source, sa);
|
||||
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
||||
source.getController(), source, sa);
|
||||
|
||||
opplist = CardLists.filter(opplist, predicate);
|
||||
ailist = CardLists.filter(ailist, predicate);
|
||||
if (opplist.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// TODO should probably sort results when targeted to use on biggest threat instead of first match
|
||||
for (Player opponent: ai.getOpponents()) {
|
||||
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(opponent)) {
|
||||
sa.getTargets().add(opponent);
|
||||
ailist.clear();
|
||||
} else {
|
||||
opplist = CardLists.filter(opplist, predicate);
|
||||
ailist = CardLists.filter(ailist, predicate);
|
||||
if (opplist.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for Raiding Party
|
||||
if (logic.equals("RaidingParty")) {
|
||||
int numAiCanSave = Math.min(CardLists.filter(ai.getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, ailist.size());
|
||||
int numOppsCanSave = Math.min(CardLists.filter(ai.getOpponents().getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, opplist.size());
|
||||
|
||||
return numOppsCanSave < opplist.size() && (ailist.size() - numAiCanSave < opplist.size() - numOppsCanSave);
|
||||
}
|
||||
|
||||
// If effect is destroying creatures and AI is about to lose, activate effect anyway no matter what!
|
||||
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
|
||||
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If effect is destroying creatures and AI is about to get low on life, activate effect anyway if difference in lost permanents not very much
|
||||
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
|
||||
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInDanger(ai, ai.getGame().getCombat()))
|
||||
&& ((ComputerUtilCard.evaluatePermanentList(ailist) - 6) >= ComputerUtilCard.evaluatePermanentList(opplist))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if only creatures are affected evaluate both lists and pass only if
|
||||
// human creatures are more valuable
|
||||
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
||||
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// test whether the human can kill the ai next turn
|
||||
Combat combat = new Combat(opponent);
|
||||
boolean containsAttacker = false;
|
||||
for (Card att : opponent.getCreaturesInPlay()) {
|
||||
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
||||
combat.addAttacker(att, ai);
|
||||
containsAttacker = containsAttacker | opplist.contains(att);
|
||||
}
|
||||
}
|
||||
if (!containsAttacker) {
|
||||
return false;
|
||||
}
|
||||
AiBlockController block = new AiBlockController(ai);
|
||||
block.assignBlockersForCombat(combat);
|
||||
|
||||
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} // only lands involved
|
||||
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
|
||||
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds") && !opplist.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
|
||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||
CardCollection oppCreatures = opponent.getCreaturesInPlay();
|
||||
if (!oppCreatures.isEmpty()) {
|
||||
if (ComputerUtilCard.evaluateCreatureList(aiCreatures) < ComputerUtilCard.evaluateCreatureList(oppCreatures) + CREATURE_EVAL_THRESHOLD) {
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(opponent)) {
|
||||
sa.getTargets().add(opponent);
|
||||
ailist.clear();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// check if the AI would lose more lands than the opponent would
|
||||
if (ComputerUtilCard.evaluatePermanentList(ailist) > ComputerUtilCard.evaluatePermanentList(opplist) + 1) {
|
||||
|
||||
// Special handling for Raiding Party
|
||||
if (logic.equals("RaidingParty")) {
|
||||
int numAiCanSave = Math.min(CardLists.filter(ai.getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, ailist.size());
|
||||
int numOppsCanSave = Math.min(CardLists.filter(ai.getOpponents().getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, opplist.size());
|
||||
|
||||
return numOppsCanSave < opplist.size() && (ailist.size() - numAiCanSave < opplist.size() - numOppsCanSave);
|
||||
}
|
||||
|
||||
// If effect is destroying creatures and AI is about to lose, activate effect anyway no matter what!
|
||||
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
|
||||
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If effect is destroying creatures and AI is about to get low on life, activate effect anyway if difference in lost permanents not very much
|
||||
if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
|
||||
&& (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInDanger(ai, ai.getGame().getCombat()))
|
||||
&& ((ComputerUtilCard.evaluatePermanentList(ailist) - 6) >= ComputerUtilCard.evaluatePermanentList(opplist))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if only creatures are affected evaluate both lists and pass only if human creatures are more valuable
|
||||
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
||||
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// test whether the human can kill the ai next turn
|
||||
Combat combat = new Combat(opponent);
|
||||
boolean containsAttacker = false;
|
||||
for (Card att : opponent.getCreaturesInPlay()) {
|
||||
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
||||
combat.addAttacker(att, ai);
|
||||
containsAttacker = containsAttacker | opplist.contains(att);
|
||||
}
|
||||
}
|
||||
if (!containsAttacker) {
|
||||
return false;
|
||||
}
|
||||
AiBlockController block = new AiBlockController(ai);
|
||||
block.assignBlockersForCombat(combat);
|
||||
|
||||
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} // only lands involved
|
||||
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
|
||||
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds")) {
|
||||
return true;
|
||||
}
|
||||
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
|
||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||
CardCollection oppCreatures = opponent.getCreaturesInPlay();
|
||||
if (!oppCreatures.isEmpty()) {
|
||||
if (ComputerUtilCard.evaluateCreatureList(aiCreatures) < ComputerUtilCard.evaluateCreatureList(oppCreatures) + CREATURE_EVAL_THRESHOLD) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// check if the AI would lose more lands than the opponent would
|
||||
if (ComputerUtilCard.evaluatePermanentList(ailist) > ComputerUtilCard.evaluatePermanentList(opplist) + 1) {
|
||||
return false;
|
||||
}
|
||||
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
|
||||
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
|
||||
return false;
|
||||
}
|
||||
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
|
||||
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import java.util.Map;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
@@ -32,7 +33,7 @@ public class DigAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
final Card host = sa.getHostCard();
|
||||
Player libraryOwner = ai;
|
||||
|
||||
@@ -120,7 +121,7 @@ public class DigAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final SpellAbility root = sa.getRootAbility();
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
@@ -19,7 +20,7 @@ public class DigMultipleAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
final Card host = sa.getHostCard();
|
||||
Player libraryOwner = ai;
|
||||
|
||||
@@ -77,7 +78,7 @@ public class DigMultipleAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
@@ -31,10 +32,8 @@ public class DigUntilAi extends SpellAbilityAi {
|
||||
chance = 1;
|
||||
}
|
||||
|
||||
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||
|
||||
Player libraryOwner = ai;
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
|
||||
if ("DontMillSelf".equals(logic)) {
|
||||
// A card that digs for specific things and puts everything revealed before it into graveyard
|
||||
@@ -92,12 +91,12 @@ public class DigUntilAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||
return randomReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (sa.isCurse()) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
@@ -60,11 +61,9 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
if (players.get(0) == ai) {
|
||||
// the ai should only be using something like this if he has
|
||||
// few cards in hand,
|
||||
// cards like this better have a good drawback to be in the
|
||||
// AIs deck
|
||||
// cards like this better have a good drawback to be in the AIs deck
|
||||
} else {
|
||||
// defined to the human, so that's fine as long the human
|
||||
// has cards
|
||||
// defined to the human, so that's fine as long the human has cards
|
||||
if (!humanHasHand) {
|
||||
return false;
|
||||
}
|
||||
@@ -170,7 +169,7 @@ public class DiscardAi extends SpellAbilityAi {
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (!discardTargetAI(ai, sa)) {
|
||||
if (mandatory && sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
|
||||
@@ -23,8 +23,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
||||
|
||||
if (tgt == null) {
|
||||
// assume we are looking to tap human's stuff
|
||||
// TODO - check for things with untap abilities, and don't tap
|
||||
// those.
|
||||
// TODO - check for things with untap abilities, and don't tap those.
|
||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (!defined.contains(opp)) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbilityMustTarget;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.util.MyRandom;
|
||||
@@ -50,6 +51,9 @@ public class FightAi extends SpellAbilityAi {
|
||||
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
||||
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||
// Filter MustTarget requirements
|
||||
StaticAbilityMustTarget.filterMustTargetCards(ai, humCreatures, sa);
|
||||
|
||||
if (humCreatures.isEmpty())
|
||||
return false; //prevent IndexOutOfBoundsException on MOJHOSTO variant
|
||||
|
||||
|
||||
@@ -16,14 +16,32 @@ public class FlipACoinAi extends SpellAbilityAi {
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
|
||||
if (sa.hasParam("AILogic")) {
|
||||
String AILogic = sa.getParam("AILogic");
|
||||
if (AILogic.equals("Never")) {
|
||||
String ailogic = sa.getParam("AILogic");
|
||||
if (ailogic.equals("Never")) {
|
||||
return false;
|
||||
} else if (AILogic.equals("PhaseOut")) {
|
||||
} else if (ailogic.equals("PhaseOut")) {
|
||||
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
||||
return false;
|
||||
}
|
||||
} else if (AILogic.equals("KillOrcs")) {
|
||||
} else if (ailogic.equals("Bangchuckers")) {
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
|
||||
return false;
|
||||
}
|
||||
sa.resetTargets();
|
||||
for (Player o : ai.getOpponents()) {
|
||||
if (sa.canTarget(o) && o.canLoseLife() && !o.cantLose()) {
|
||||
sa.getTargets().add(o);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (Card c : ai.getOpponents().getCreaturesInPlay()) {
|
||||
if (sa.canTarget(c)) {
|
||||
sa.getTargets().add(c);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (ailogic.equals("KillOrcs")) {
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -29,11 +29,10 @@ public class GameLossAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
// Phage the Untouchable
|
||||
// (Final Fortune would need to attach it's delayed trigger to a
|
||||
// specific turn, which can't be done yet)
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = ai.getGame().getCombat().getDefenderPlayerByAttacker(sa.getHostCard());
|
||||
|
||||
if (!mandatory && opp.cantLose()) {
|
||||
return false;
|
||||
|
||||
56
forge-ai/src/main/java/forge/ai/ability/LearnAi.java
Normal file
56
forge-ai/src/main/java/forge/ai/ability/LearnAi.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class LearnAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
// For the time being, Learn is treated as universally positive due to being optional
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return mandatory || canPlayAI(aiPlayer, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
return canPlayAI(aiPlayer, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Card chooseCardToLearn(CardCollection options, Player ai, SpellAbility sa) {
|
||||
CardCollection sideboard = CardLists.filter(options, CardPredicates.inZone(ZoneType.Sideboard));
|
||||
CardCollection hand = CardLists.filter(options, CardPredicates.inZone(ZoneType.Hand));
|
||||
hand.remove(sa.getHostCard()); // this card will be used in the process, don't consider it for discard
|
||||
|
||||
CardCollection lessons = CardLists.filter(sideboard, CardPredicates.isType("Lesson"));
|
||||
CardCollection goodDiscards = ((PlayerControllerAi)ai.getController()).getAi().getCardsToDiscard(1, 1, hand, sa);
|
||||
|
||||
if (!lessons.isEmpty()) {
|
||||
return ComputerUtilCard.getBestAI(lessons);
|
||||
} else if (!goodDiscards.isEmpty()) {
|
||||
return ComputerUtilCard.getWorstAI(goodDiscards);
|
||||
}
|
||||
|
||||
// Don't choose anything if there's no good option
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -19,7 +20,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
final int myLife = aiPlayer.getLife();
|
||||
Player opponent = aiPlayer.getWeakestOpponent();
|
||||
Player opponent = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||
final int hLife = opponent.getLife();
|
||||
|
||||
if (!aiPlayer.canGainLife()) {
|
||||
@@ -75,7 +76,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
||||
final boolean mandatory) {
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
@@ -154,7 +155,7 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
|
||||
final boolean mandatory) {
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||
|
||||
@@ -17,7 +17,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final int myLife = ai.getLife();
|
||||
final Player opponent = ai.getWeakestOpponent();
|
||||
final Player opponent = ai.getStrongestOpponent();
|
||||
final int hlife = opponent.getLife();
|
||||
final String amountStr = sa.getParam("LifeAmount");
|
||||
|
||||
@@ -36,8 +36,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO handle proper calculation of X values based on Cost and what
|
||||
// would be paid
|
||||
// TODO handle proper calculation of X values based on Cost and what would be paid
|
||||
int amount;
|
||||
// we shouldn't have to worry too much about PayX for SetLife
|
||||
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||
@@ -58,11 +57,9 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
if (tgt.canOnlyTgtOpponent()) {
|
||||
sa.getTargets().add(opponent);
|
||||
// if we can only target the human, and the Human's life
|
||||
// would
|
||||
// go up, don't play it.
|
||||
// would go up, don't play it.
|
||||
// possibly add a combo here for Magister Sphinx and
|
||||
// Higedetsu's
|
||||
// (sp?) Second Rite
|
||||
// Higedetsu's (sp?) Second Rite
|
||||
if ((amount > hlife) || !opponent.canLoseLife()) {
|
||||
return false;
|
||||
}
|
||||
@@ -81,8 +78,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
if (sa.getParam("Defined").equals("Player")) {
|
||||
if (amount == 0) {
|
||||
return false;
|
||||
} else if (myLife > amount) { // will decrease computer's
|
||||
// life
|
||||
} else if (myLife > amount) { // will decrease computer's life
|
||||
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
|
||||
return false;
|
||||
}
|
||||
@@ -104,7 +100,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final int myLife = ai.getLife();
|
||||
final Player opponent = ai.getWeakestOpponent();
|
||||
final Player opponent = ai.getStrongestOpponent();
|
||||
final int hlife = opponent.getLife();
|
||||
final Card source = sa.getHostCard();
|
||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||
@@ -133,8 +129,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// If the Target is gaining life, target self.
|
||||
// if the Target is modifying how much life is gained, this needs to
|
||||
// be handled better
|
||||
// if the Target is modifying how much life is gained, this needs to be handled better
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
|
||||
@@ -30,13 +30,12 @@ public class PowerExchangeAi extends SpellAbilityAi {
|
||||
sa.resetTargets();
|
||||
|
||||
List<Card> list =
|
||||
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
// AI won't try to grab cards that are filtered out of AI decks on
|
||||
// purpose
|
||||
CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
||||
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa);
|
||||
return !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa) && c.getController() != ai;
|
||||
}
|
||||
});
|
||||
CardLists.sortByPowerAsc(list);
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
@@ -201,7 +202,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
final int newPower = card.getNetCombatDamage() + attack;
|
||||
//int defense = getNumDefense(sa);
|
||||
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
|
||||
|
||||
@@ -48,12 +48,6 @@ public class PumpAllAi extends PumpAiBase {
|
||||
}
|
||||
}
|
||||
|
||||
final int power = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumAtt"), sa);
|
||||
final int defense = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumDef"), sa);
|
||||
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<>();
|
||||
|
||||
final PhaseType phase = game.getPhaseHandler().getPhase();
|
||||
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
}
|
||||
@@ -63,16 +57,10 @@ public class PumpAllAi extends PumpAiBase {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("ValidCards")) {
|
||||
valid = sa.getParam("ValidCards");
|
||||
}
|
||||
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
CardCollection comp = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
||||
CardCollection human = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
||||
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final Player opp = ai.getStrongestOpponent();
|
||||
|
||||
if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
@@ -85,6 +73,18 @@ public class PumpAllAi extends PumpAiBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
final int power = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumAtt"), sa);
|
||||
final int defense = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumDef"), sa);
|
||||
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<>();
|
||||
final PhaseType phase = game.getPhaseHandler().getPhase();
|
||||
|
||||
if (sa.hasParam("ValidCards")) {
|
||||
valid = sa.getParam("ValidCards");
|
||||
}
|
||||
|
||||
CardCollection comp = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
||||
CardCollection human = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
||||
|
||||
if (!game.getStack().isEmpty() && !sa.isCurse()) {
|
||||
return pumpAgainstRemoval(ai, sa, comp);
|
||||
}
|
||||
@@ -139,8 +139,7 @@ public class PumpAllAi extends PumpAiBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
// evaluate both lists and pass only if human creatures are more
|
||||
// valuable
|
||||
// evaluate both lists and pass only if human creatures are more valuable
|
||||
return (ComputerUtilCard.evaluateCreatureList(comp) + 200) < ComputerUtilCard.evaluateCreatureList(human);
|
||||
} // end Curse
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.AiController;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
@@ -14,7 +15,7 @@ public class RepeatAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
if (!opp.canBeTargetedBy(sa)) {
|
||||
@@ -44,9 +45,8 @@ public class RepeatAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(opp);
|
||||
|
||||
@@ -16,7 +16,6 @@ import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.TextUtil;
|
||||
@@ -67,7 +66,7 @@ public class RepeatEachAi extends SpellAbilityAi {
|
||||
return false;
|
||||
} else if ("AllPlayerLoseLife".equals(logic)) {
|
||||
final Card source = sa.getHostCard();
|
||||
AbilitySub repeat = sa.getAdditionalAbility("RepeatSubAbility");
|
||||
SpellAbility repeat = sa.getAdditionalAbility("RepeatSubAbility");
|
||||
|
||||
String svar = repeat.getSVar(repeat.getParam("LifeAmount"));
|
||||
// replace RememberedPlayerCtrl with YouCtrl
|
||||
|
||||
@@ -20,9 +20,6 @@ import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
public class SacrificeAi extends SpellAbilityAi {
|
||||
// **************************************************************
|
||||
// *************************** Sacrifice ***********************
|
||||
// **************************************************************
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
@@ -48,8 +45,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// Improve AI for triggers. If source is a creature with:
|
||||
// When ETB, sacrifice a creature. Check to see if the AI has something
|
||||
// to sacrifice
|
||||
// When ETB, sacrifice a creature. Check to see if the AI has something to sacrifice
|
||||
|
||||
// Eventually, we can call the trigger of ETB abilities with not
|
||||
// mandatory as part of the checks to cast something
|
||||
@@ -58,12 +54,11 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa) {
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
final boolean destroy = sa.hasParam("Destroy");
|
||||
|
||||
Player opp = ai.getWeakestOpponent();
|
||||
Player opp = ai.getStrongestOpponent();
|
||||
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
@@ -109,8 +104,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount));
|
||||
}
|
||||
|
||||
final int half = (amount / 2) + (amount % 2); // Half of amount
|
||||
// rounded up
|
||||
final int half = (amount / 2) + (amount % 2); // Half of amount rounded up
|
||||
|
||||
// If the Human has at least half rounded up of the amount to be
|
||||
// sacrificed, cast the spell
|
||||
@@ -130,8 +124,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
// If Sacrifice hits both players:
|
||||
// Only cast it if Human has the full amount of valid
|
||||
// Only cast it if AI doesn't have the full amount of Valid
|
||||
// TODO: Cast if the type is favorable: my "worst" valid is
|
||||
// worse than his "worst" valid
|
||||
// TODO: Cast if the type is favorable: my "worst" valid is worse than his "worst" valid
|
||||
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
|
||||
int amount = AbilityUtils.calculateAmount(source, num, sa);
|
||||
|
||||
|
||||
@@ -2,17 +2,12 @@ package forge.ai.ability;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
public class SacrificeAllAi extends SpellAbilityAi {
|
||||
|
||||
@@ -22,22 +17,7 @@ public class SacrificeAllAi extends SpellAbilityAi {
|
||||
// based on what the expected targets could be
|
||||
final Cost abCost = sa.getPayCosts();
|
||||
final Card source = sa.getHostCard();
|
||||
String valid = "";
|
||||
|
||||
if (sa.hasParam("ValidCards")) {
|
||||
valid = sa.getParam("ValidCards");
|
||||
}
|
||||
|
||||
if (valid.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
valid = TextUtil.fastReplace(valid, "X", Integer.toString(xPay));
|
||||
}
|
||||
|
||||
CardCollection humanlist =
|
||||
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||
CardCollection computerlist =
|
||||
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if (abCost != null) {
|
||||
// AI currently disabled for some costs
|
||||
@@ -45,30 +25,20 @@ public class SacrificeAllAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (logic.equals("HellionEruption")) {
|
||||
if (ai.getCreaturesInPlay().size() < 5 || ai.getCreaturesInPlay().size() * 150 < ComputerUtilCard.evaluateCreatureList(ai.getCreaturesInPlay())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!DestroyAllAi.doMassRemovalLogic(ai, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
|
||||
// if only creatures are affected evaluate both lists and pass only if
|
||||
// human creatures are more valuable
|
||||
if ((CardLists.getNotType(humanlist, "Creature").size() == 0) && (CardLists.getNotType(computerlist, "Creature").size() == 0)) {
|
||||
if ((ComputerUtilCard.evaluateCreatureList(computerlist) + 200) >= ComputerUtilCard
|
||||
.evaluateCreatureList(humanlist)) {
|
||||
return false;
|
||||
}
|
||||
} // only lands involved
|
||||
else if ((CardLists.getNotType(humanlist, "Land").size() == 0) && (CardLists.getNotType(computerlist, "Land").size() == 0)) {
|
||||
if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 1) >= ComputerUtilCard
|
||||
.evaluatePermanentList(humanlist)) {
|
||||
return false;
|
||||
}
|
||||
} // otherwise evaluate both lists by CMC and pass only if human
|
||||
// permanents are more valuable
|
||||
else if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 3) >= ComputerUtilCard
|
||||
.evaluatePermanentList(humanlist)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((MyRandom.getRandom().nextFloat() < .9667) && chance);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,12 @@ package forge.ai.ability;
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.Card.SplitCMCMode;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
@@ -47,6 +45,11 @@ public class ScryAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
// For Brain in a Jar, avoid competing against the other ability in the opponent's EOT.
|
||||
if ("BrainJar".equals(sa.getParam("AILogic"))) {
|
||||
return ph.getPhase().isAfter(PhaseType.MAIN2);
|
||||
}
|
||||
|
||||
// if the Scry ability requires tapping and has a mana cost, it's best done at the end of opponent's turn
|
||||
// and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
|
||||
// try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
|
||||
@@ -102,56 +105,9 @@ public class ScryAi extends SpellAbilityAi {
|
||||
if ("Never".equals(aiLogic)) {
|
||||
return false;
|
||||
} else if ("BrainJar".equals(aiLogic)) {
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
int counterNum = source.getCounters(CounterEnumType.CHARGE);
|
||||
// no need for logic
|
||||
if (counterNum == 0) {
|
||||
return false;
|
||||
}
|
||||
int libsize = ai.getCardsIn(ZoneType.Library).size();
|
||||
|
||||
final CardCollection hand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.or(
|
||||
CardPredicates.isType("Instant"), CardPredicates.isType("Sorcery")));
|
||||
if (!hand.isEmpty()) {
|
||||
// has spell that can be cast in hand with put ability
|
||||
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum + 1)).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// has spell that can be cast if one counter is removed
|
||||
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum)).isEmpty()) {
|
||||
sa.setXManaCostPaid(1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
final CardCollection library = CardLists.filter(ai.getCardsIn(ZoneType.Library), Predicates.or(
|
||||
CardPredicates.isType("Instant"), CardPredicates.isType("Sorcery")));
|
||||
if (!library.isEmpty()) {
|
||||
// get max cmc of instant or sorceries in the libary
|
||||
int maxCMC = 0;
|
||||
for (final Card c : library) {
|
||||
int v = c.getCMC();
|
||||
if (c.isSplitCard()) {
|
||||
v = Math.max(c.getCMC(SplitCMCMode.LeftSplitCMC), c.getCMC(SplitCMCMode.RightSplitCMC));
|
||||
}
|
||||
if (v > maxCMC) {
|
||||
maxCMC = v;
|
||||
}
|
||||
}
|
||||
// there is a spell with more CMC, no need to remove counter
|
||||
if (counterNum + 1 < maxCMC) {
|
||||
return false;
|
||||
}
|
||||
int maxToRemove = counterNum - maxCMC + 1;
|
||||
// no Scry 0, even if its catched from later stuff
|
||||
if (maxToRemove <= 0) {
|
||||
return false;
|
||||
}
|
||||
sa.setXManaCostPaid(maxToRemove);
|
||||
} else {
|
||||
// no Instant or Sorceries anymore, just scry
|
||||
sa.setXManaCostPaid(Math.min(counterNum, libsize));
|
||||
}
|
||||
return SpecialCardAi.BrainInAJar.consider(ai, sa);
|
||||
} else if ("MultipleChoice".equals(aiLogic)) {
|
||||
return SpecialCardAi.MultipleChoice.consider(ai, sa);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@ public class TapAi extends TapAiBase {
|
||||
// Set PayX here to maximum value.
|
||||
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
||||
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
|
||||
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
|
||||
}
|
||||
|
||||
sa.resetTargets();
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.List;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
@@ -109,7 +110,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
* @return a boolean.
|
||||
*/
|
||||
protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) {
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
final Game game = ai.getGame();
|
||||
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||
tapList = CardLists.getTargetableCards(tapList, sa);
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
@@ -28,7 +29,7 @@ public class TwoPilesAi extends SpellAbilityAi {
|
||||
valid = sa.getParam("ValidCards");
|
||||
}
|
||||
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
|
||||
@@ -56,7 +56,6 @@ public class UnattachAllAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
final Card card = sa.getHostCard();
|
||||
final Player opp = ai.getWeakestOpponent();
|
||||
// Check if there are any valid targets
|
||||
List<GameObject> targets = new ArrayList<>();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
@@ -66,8 +65,8 @@ public class UnattachAllAi extends SpellAbilityAi {
|
||||
|
||||
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
|
||||
Card newTarget = (Card) targets.get(0);
|
||||
//don't equip human creatures
|
||||
if (newTarget.getController().equals(opp)) {
|
||||
//don't equip opponent creatures
|
||||
if (!newTarget.getController().equals(ai)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.Map;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
@@ -130,7 +131,8 @@ public class UntapAi extends SpellAbilityAi {
|
||||
Player targetController = ai;
|
||||
|
||||
if (sa.isCurse()) {
|
||||
targetController = ai.getWeakestOpponent();
|
||||
// TODO search through all opponents, may need to check if different controllers allowed
|
||||
targetController = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
}
|
||||
|
||||
CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa);
|
||||
@@ -149,8 +151,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
CardCollection untapList = targetUntapped ? list : CardLists.filter(list, Presets.TAPPED);
|
||||
// filter out enchantments and planeswalkers, their tapped state doesn't
|
||||
// matter.
|
||||
// filter out enchantments and planeswalkers, their tapped state doesn't matter.
|
||||
final String[] tappablePermanents = {"Creature", "Land", "Artifact"};
|
||||
untapList = CardLists.getValidCards(untapList, tappablePermanents, source.getController(), source, sa);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ability.ChangeZoneAi;
|
||||
import forge.ai.ability.ExploreAi;
|
||||
import forge.ai.ability.LearnAi;
|
||||
import forge.ai.simulation.GameStateEvaluator.Score;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.ApiType;
|
||||
@@ -423,6 +424,8 @@ public class SpellAbilityPicker {
|
||||
}
|
||||
if (sa.getApi() == ApiType.Explore) {
|
||||
return ExploreAi.shouldPutInGraveyard(fetchList, decider);
|
||||
} else if (sa.getApi() == ApiType.Learn) {
|
||||
return LearnAi.chooseCardToLearn(fetchList, decider, sa);
|
||||
} else {
|
||||
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.40-SNAPSHOT</version>
|
||||
<version>1.6.40</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-core</artifactId>
|
||||
|
||||
@@ -121,6 +121,9 @@ public final class ImageKeys {
|
||||
// if there's a 1st art variant try without it for .fullborder images
|
||||
file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder"));
|
||||
if (file != null) { return file; }
|
||||
// if there's a 1st art variant try with it for .fullborder images
|
||||
file = findFile(dir, fullborderFile.replaceAll("[0-9]*.fullborder", "1.fullborder"));
|
||||
if (file != null) { return file; }
|
||||
// if there's an art variant try without it for .full images
|
||||
file = findFile(dir, filename.replaceAll("[0-9].full",".full"));
|
||||
if (file != null) { return file; }
|
||||
|
||||
@@ -2,7 +2,6 @@ package forge;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -79,16 +78,30 @@ public class StaticData {
|
||||
this.customEditions = new CardEdition.Collection(new CardEdition.Reader(new File(customEditionsFolder)));
|
||||
this.prefferedArt = prefferedArt;
|
||||
lastInstance = this;
|
||||
List<String> funnyCards = new ArrayList<>();
|
||||
List<String> filtered = new ArrayList<>();
|
||||
|
||||
{
|
||||
final Map<String, CardRules> regularCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
final Map<String, CardRules> variantsCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
final Map<String, CardRules> customizedCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
for (CardEdition e : editions) {
|
||||
if (e.getType() == CardEdition.Type.FUNNY || e.getBorderColor() == CardEdition.BorderColor.SILVER) {
|
||||
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
|
||||
funnyCards.add(cis.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (CardRules card : cardReader.loadCards()) {
|
||||
if (null == card) continue;
|
||||
|
||||
final String cardName = card.getName();
|
||||
|
||||
if (!loadNonLegalCards && !card.getType().isBasicLand() && funnyCards.contains(cardName))
|
||||
filtered.add(cardName);
|
||||
|
||||
if (card.isVariant()) {
|
||||
variantsCards.put(cardName, card);
|
||||
} else {
|
||||
@@ -104,15 +117,18 @@ public class StaticData {
|
||||
}
|
||||
}
|
||||
|
||||
if (!filtered.isEmpty()) {
|
||||
Collections.sort(filtered);
|
||||
}
|
||||
|
||||
commonCards = new CardDb(regularCards, editions);
|
||||
variantCards = new CardDb(variantsCards, editions);
|
||||
customCards = new CardDb(customizedCards, customEditions);
|
||||
commonCards = new CardDb(regularCards, editions, filtered);
|
||||
variantCards = new CardDb(variantsCards, editions, filtered);
|
||||
customCards = new CardDb(customizedCards, customEditions, filtered);
|
||||
|
||||
//must initialize after establish field values for the sake of card image logic
|
||||
commonCards.initialize(false, false, enableUnknownCards, loadNonLegalCards);
|
||||
variantCards.initialize(false, false, enableUnknownCards, loadNonLegalCards);
|
||||
customCards.initialize(false, false, enableUnknownCards, loadNonLegalCards);
|
||||
commonCards.initialize(false, false, enableUnknownCards);
|
||||
variantCards.initialize(false, false, enableUnknownCards);
|
||||
customCards.initialize(false, false, enableUnknownCards);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -125,14 +141,6 @@ public class StaticData {
|
||||
}
|
||||
allTokens = new TokenDb(tokens, editions);
|
||||
}
|
||||
|
||||
{
|
||||
if (customCards.getAllCards().size() > 0) {
|
||||
Collection<PaperCard> paperCards = customCards.getAllCards();
|
||||
for(PaperCard p: paperCards)
|
||||
commonCards.addCard(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static StaticData instance() {
|
||||
|
||||
@@ -32,6 +32,7 @@ import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import forge.StaticData;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
@@ -55,6 +56,8 @@ import forge.util.TextUtil;
|
||||
public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
public final static String foilSuffix = "+";
|
||||
public final static char NameSetSeparator = '|';
|
||||
private final String exlcudedCardName = "Concentrate";
|
||||
private final String exlcudedCardSet = "DS0";
|
||||
|
||||
// need this to obtain cardReference by name+set+artindex
|
||||
private final ListMultimap<String, PaperCard> allCardsByName = Multimaps.newListMultimap(new TreeMap<>(String.CASE_INSENSITIVE_ORDER), CollectionSuppliers.arrayLists());
|
||||
@@ -66,8 +69,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
private final Map<String, String> alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
|
||||
private final Map<String, Integer> artIds = new HashMap<>();
|
||||
|
||||
private final Collection<PaperCard> roAllCards = Collections.unmodifiableCollection(allCardsByName.values());
|
||||
private final CardEdition.Collection editions;
|
||||
private List<String> filtered;
|
||||
|
||||
public enum SetPreference {
|
||||
Latest(false),
|
||||
@@ -134,12 +137,15 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
}
|
||||
}
|
||||
|
||||
public CardDb(Map<String, CardRules> rules, CardEdition.Collection editions0) {
|
||||
public CardDb(Map<String, CardRules> rules, CardEdition.Collection editions0, List<String> filteredCards) {
|
||||
this.filtered = filteredCards;
|
||||
this.rulesByName = rules;
|
||||
this.editions = editions0;
|
||||
|
||||
// create faces list from rules
|
||||
for (final CardRules rule : rules.values() ) {
|
||||
if (filteredCards.contains(rule.getName()) && !exlcudedCardName.equalsIgnoreCase(rule.getName()))
|
||||
continue;
|
||||
final ICardFace main = rule.getMainPart();
|
||||
facesByName.put(main.getName(), main);
|
||||
if (main.getAltName() != null) {
|
||||
@@ -155,6 +161,10 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
}
|
||||
}
|
||||
|
||||
private ListMultimap<String, PaperCard> getAllCardsByName() {
|
||||
return allCardsByName;
|
||||
}
|
||||
|
||||
private void addSetCard(CardEdition e, CardInSet cis, CardRules cr) {
|
||||
int artIdx = 1;
|
||||
String key = e.getCode() + "/" + cis.name;
|
||||
@@ -182,33 +192,24 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
reIndex();
|
||||
}
|
||||
|
||||
public void initialize(boolean logMissingPerEdition, boolean logMissingSummary, boolean enableUnknownCards, boolean loadNonLegalCards) {
|
||||
public void initialize(boolean logMissingPerEdition, boolean logMissingSummary, boolean enableUnknownCards) {
|
||||
Set<String> allMissingCards = new LinkedHashSet<>();
|
||||
List<String> missingCards = new ArrayList<>();
|
||||
CardEdition upcomingSet = null;
|
||||
Date today = new Date();
|
||||
List<String> skippedCardName = new ArrayList<>();
|
||||
|
||||
for (CardEdition e : editions.getOrderedEditions()) {
|
||||
boolean coreOrExpSet = e.getType() == CardEdition.Type.CORE || e.getType() == CardEdition.Type.EXPANSION;
|
||||
boolean isCoreExpSet = coreOrExpSet || e.getType() == CardEdition.Type.REPRINT;
|
||||
//todo sets with nonlegal cards should have tags in them so we don't need to specify the code here
|
||||
boolean skip = !loadNonLegalCards && (e.getType() == CardEdition.Type.FUNNY || e.getBorderColor() == CardEdition.BorderColor.SILVER);
|
||||
if (logMissingPerEdition && isCoreExpSet) {
|
||||
System.out.print(e.getName() + " (" + e.getAllCardsInSet().size() + " cards)");
|
||||
}
|
||||
if (coreOrExpSet && e.getDate().after(today) && upcomingSet == null) {
|
||||
if (skip)
|
||||
upcomingSet = e;
|
||||
upcomingSet = e;
|
||||
}
|
||||
|
||||
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
|
||||
CardRules cr = rulesByName.get(cis.name);
|
||||
if (cr != null && !cr.getType().isBasicLand() && skip) {
|
||||
skippedCardName.add(cis.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cr != null) {
|
||||
addSetCard(e, cis, cr);
|
||||
}
|
||||
@@ -244,7 +245,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
if (!contains(cr.getName())) {
|
||||
if (upcomingSet != null) {
|
||||
addCard(new PaperCard(cr, upcomingSet.getCode(), CardRarity.Unknown, 1));
|
||||
} else if(enableUnknownCards && !skippedCardName.contains(cr.getName())) {
|
||||
} else if(enableUnknownCards) {
|
||||
System.err.println("The card " + cr.getName() + " was not assigned to any set. Adding it to UNKNOWN set... to fix see res/editions/ folder. ");
|
||||
addCard(new PaperCard(cr, CardEdition.UNKNOWN.getCode(), CardRarity.Special, 1));
|
||||
}
|
||||
@@ -255,6 +256,9 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
}
|
||||
|
||||
public void addCard(PaperCard paperCard) {
|
||||
if (excludeCard(paperCard.getName(), paperCard.getEdition()))
|
||||
return;
|
||||
|
||||
allCardsByName.put(paperCard.getName(), paperCard);
|
||||
|
||||
if (paperCard.getRules().getSplitType() == CardSplitType.None) { return; }
|
||||
@@ -268,11 +272,22 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
allCardsByName.put(paperCard.getRules().getMainPart().getName(), paperCard);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean excludeCard(String cardName, String cardEdition) {
|
||||
if (filtered.isEmpty())
|
||||
return false;
|
||||
if (filtered.contains(cardName)) {
|
||||
if (exlcudedCardSet.equalsIgnoreCase(cardEdition) && exlcudedCardName.equalsIgnoreCase(cardName))
|
||||
return true;
|
||||
else if (!exlcudedCardName.equalsIgnoreCase(cardName))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private void reIndex() {
|
||||
uniqueCardsByName.clear();
|
||||
for (Entry<String, Collection<PaperCard>> kv : allCardsByName.asMap().entrySet()) {
|
||||
uniqueCardsByName.put(kv.getKey(), getFirstWithImage(kv.getValue()));
|
||||
for (Entry<String, Collection<PaperCard>> kv : getAllCardsByName().asMap().entrySet()) {
|
||||
PaperCard pc = getFirstWithImage(kv.getValue());
|
||||
uniqueCardsByName.put(kv.getKey(), pc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -561,6 +576,17 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
return uniqueCardsByName.values();
|
||||
}
|
||||
|
||||
public Collection<PaperCard> getUniqueCardsNoAlt() {
|
||||
return Maps.filterEntries(this.uniqueCardsByName, new Predicate<Entry<String, PaperCard>>() {
|
||||
@Override
|
||||
public boolean apply(Entry<String, PaperCard> e) {
|
||||
if (e == null)
|
||||
return false;
|
||||
return e.getKey().equals(e.getValue().getName());
|
||||
}
|
||||
}).values();
|
||||
}
|
||||
|
||||
public PaperCard getUniqueByName(final String name) {
|
||||
return uniqueCardsByName.get(getName(name));
|
||||
}
|
||||
@@ -575,11 +601,20 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
|
||||
@Override
|
||||
public Collection<PaperCard> getAllCards() {
|
||||
return roAllCards;
|
||||
return Collections.unmodifiableCollection(getAllCardsByName().values());
|
||||
}
|
||||
|
||||
public Collection<PaperCard> getAllCardsNoAlt() {
|
||||
return Multimaps.filterEntries(getAllCardsByName(), new Predicate<Entry<String, PaperCard>>() {
|
||||
@Override
|
||||
public boolean apply(Entry<String, PaperCard> entry) {
|
||||
return entry.getKey().equals(entry.getValue().getName());
|
||||
}
|
||||
}).values();
|
||||
}
|
||||
|
||||
public Collection<PaperCard> getAllNonPromoCards() {
|
||||
return Lists.newArrayList(Iterables.filter(this.roAllCards, new Predicate<PaperCard>() {
|
||||
return Lists.newArrayList(Iterables.filter(getAllCards(), new Predicate<PaperCard>() {
|
||||
@Override
|
||||
public boolean apply(final PaperCard paperCard) {
|
||||
CardEdition edition = null;
|
||||
@@ -602,13 +637,27 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
|
||||
@Override
|
||||
public List<PaperCard> getAllCards(String cardName) {
|
||||
return allCardsByName.get(getName(cardName));
|
||||
return getAllCardsByName().get(getName(cardName));
|
||||
}
|
||||
|
||||
public List<PaperCard> getAllCardsNoAlt(String cardName) {
|
||||
return Lists.newArrayList(Multimaps.filterEntries(getAllCardsByName(), new Predicate<Entry<String, PaperCard>>() {
|
||||
@Override
|
||||
public boolean apply(Entry<String, PaperCard> entry) {
|
||||
return entry.getKey().equals(entry.getValue().getName());
|
||||
}
|
||||
}).get(getName(cardName)));
|
||||
}
|
||||
|
||||
/** Returns a modifiable list of cards matching the given predicate */
|
||||
@Override
|
||||
public List<PaperCard> getAllCards(Predicate<PaperCard> predicate) {
|
||||
return Lists.newArrayList(Iterables.filter(this.roAllCards, predicate));
|
||||
return Lists.newArrayList(Iterables.filter(getAllCards(), predicate));
|
||||
}
|
||||
|
||||
/** Returns a modifiable list of cards matching the given predicate */
|
||||
public List<PaperCard> getAllCardsNoAlt(Predicate<PaperCard> predicate) {
|
||||
return Lists.newArrayList(Iterables.filter(getAllCardsNoAlt(), predicate));
|
||||
}
|
||||
|
||||
// Do I want a foiled version of these cards?
|
||||
@@ -630,12 +679,12 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||
|
||||
@Override
|
||||
public boolean contains(String name) {
|
||||
return allCardsByName.containsKey(getName(name));
|
||||
return getAllCardsByName().containsKey(getName(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<PaperCard> iterator() {
|
||||
return this.roAllCards.iterator();
|
||||
return getAllCards().iterator();
|
||||
}
|
||||
|
||||
public Predicate<? super PaperCard> wasPrintedInSets(List<String> setCodes) {
|
||||
|
||||
@@ -116,7 +116,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
}
|
||||
|
||||
// reserved names of sections inside edition files, that are not parsed as cards
|
||||
private static final List<String> reservedSectionNames = ImmutableList.of("metadata", "tokens");
|
||||
private static final List<String> reservedSectionNames = ImmutableList.of("metadata", "tokens", "other");
|
||||
|
||||
// commonly used printsheets with collector number
|
||||
public enum EditionSectionWithCollectorNumbers {
|
||||
@@ -127,7 +127,8 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
EXTENDED_ART("extended art"),
|
||||
ALTERNATE_ART("alternate art"),
|
||||
BUY_A_BOX("buy a box"),
|
||||
PROMO("promo");
|
||||
PROMO("promo"),
|
||||
BOX_TOPPER("box topper");
|
||||
|
||||
private final String name;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package forge.card;
|
||||
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -28,6 +28,9 @@ import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostShard;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
import static forge.card.MagicColor.Constant.*;
|
||||
import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
|
||||
|
||||
/**
|
||||
* A collection of methods containing full
|
||||
* meta and gameplay properties of a card.
|
||||
@@ -42,6 +45,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
private ICardFace otherPart;
|
||||
private CardAiHints aiHints;
|
||||
private ColorSet colorIdentity;
|
||||
private ColorSet deckbuildingColors;
|
||||
private String meldWith;
|
||||
private String partnerWith;
|
||||
|
||||
@@ -581,4 +585,25 @@ public final class CardRules implements ICardCharacteristics {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ColorSet getDeckbuildingColors() {
|
||||
if (deckbuildingColors == null) {
|
||||
byte colors = 0;
|
||||
if (mainPart.getType().isLand()) {
|
||||
colors = getColorIdentity().getColor();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (containsIgnoreCase(mainPart.getOracleText(), BASIC_LANDS.get(i))) {
|
||||
colors |= 1 << i;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
colors = getColor().getColor();
|
||||
if (getOtherPart() != null) {
|
||||
colors |= getOtherPart().getManaCost().getColorProfile();
|
||||
}
|
||||
}
|
||||
deckbuildingColors = ColorSet.fromMask(colors);
|
||||
}
|
||||
return deckbuildingColors;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
public enum Supertype {
|
||||
Basic,
|
||||
Elite,
|
||||
Host,
|
||||
Legendary,
|
||||
Snow,
|
||||
Ongoing,
|
||||
|
||||
@@ -138,23 +138,11 @@ public final class MagicColor {
|
||||
public static final ImmutableList<String> BASIC_LANDS = ImmutableList.of("Plains", "Island", "Swamp", "Mountain", "Forest");
|
||||
public static final ImmutableList<String> SNOW_LANDS = ImmutableList.of("Snow-Covered Plains", "Snow-Covered Island", "Snow-Covered Swamp", "Snow-Covered Mountain", "Snow-Covered Forest");
|
||||
public static final ImmutableMap<String, String> ANY_COLOR_CONVERSION = new ImmutableMap.Builder<String, String>()
|
||||
.put("ManaColorConversion", "Additive")
|
||||
.put("WhiteConversion", "Color")
|
||||
.put("BlueConversion", "Color")
|
||||
.put("BlackConversion", "Color")
|
||||
.put("RedConversion", "Color")
|
||||
.put("GreenConversion", "Color")
|
||||
.put("ColorlessConversion", "Color")
|
||||
.put("ManaConversion", "AnyType->AnyColor")
|
||||
.build();
|
||||
|
||||
public static final ImmutableMap<String, String> ANY_TYPE_CONVERSION = new ImmutableMap.Builder<String, String>()
|
||||
.put("ManaColorConversion", "Additive")
|
||||
.put("WhiteConversion", "Type")
|
||||
.put("BlueConversion", "Type")
|
||||
.put("BlackConversion", "Type")
|
||||
.put("RedConversion", "Type")
|
||||
.put("GreenConversion", "Type")
|
||||
.put("ColorlessConversion", "Type")
|
||||
.put("ManaConversion", "AnyType->AnyType")
|
||||
.build();
|
||||
/**
|
||||
* Private constructor to prevent instantiation.
|
||||
|
||||
@@ -31,7 +31,6 @@ public class PrintSheet {
|
||||
|
||||
for(CardEdition edition : editions) {
|
||||
for(PrintSheet ps : edition.getPrintSheetsBySection()) {
|
||||
System.out.println(ps.name);
|
||||
sheets.add(ps.name, ps);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,18 @@ public abstract class ManaAtom {
|
||||
return 0; // generic
|
||||
}
|
||||
|
||||
public static byte fromConversion(String s) {
|
||||
switch (s) {
|
||||
case "AnyColor": return ALL_MANA_COLORS;
|
||||
case "AnyType": return ALL_MANA_TYPES;
|
||||
}
|
||||
byte b = 0;
|
||||
for (char c : s.toCharArray()) {
|
||||
b |= fromName(c);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
public static int getIndexOfFirstManaType(final byte color){
|
||||
for (int i = 0; i < MANATYPES.length; i++) {
|
||||
if ((color & MANATYPES[i]) != 0) {
|
||||
|
||||
@@ -64,7 +64,10 @@ public enum ManaCostShard {
|
||||
PR(ManaAtom.RED | ManaAtom.OR_2_LIFE, "P/R", "PR"),
|
||||
PG(ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "P/G", "PG"),
|
||||
|
||||
X(ManaAtom.IS_X, "X");
|
||||
X(ManaAtom.IS_X, "X"),
|
||||
|
||||
// Colored only X, each color can be used to pay for this only once (for Emblazoned Golem)
|
||||
COLORED_X(ManaAtom.WHITE | ManaAtom.BLUE | ManaAtom.BLACK | ManaAtom.RED | ManaAtom.GREEN | ManaAtom.IS_X, "1");
|
||||
|
||||
private final int shard;
|
||||
|
||||
@@ -297,7 +300,7 @@ public enum ManaCostShard {
|
||||
}
|
||||
|
||||
public boolean isColor(byte colorCode) {
|
||||
return (colorCode & this.shard) > 0;
|
||||
return (colorCode & this.shard) > 0;
|
||||
}
|
||||
|
||||
public boolean canBePaidWithManaOfColor(byte colorCode) {
|
||||
|
||||
@@ -489,15 +489,6 @@ public abstract class DeckGeneratorBase {
|
||||
return dLands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all dual lands that do not match this color combo.
|
||||
*
|
||||
* @return dual land names
|
||||
*/
|
||||
protected List<String> getInverseDualLandList() {
|
||||
return inverseDLands;
|
||||
}
|
||||
|
||||
private void addCardNameToList(String cardName, List<String> cardNameList) {
|
||||
if (pool.contains(cardName)) { //avoid adding card if it's not in pool
|
||||
cardNameList.add(cardName);
|
||||
|
||||
@@ -113,8 +113,12 @@ public class CardTranslation {
|
||||
|
||||
public static void buildOracleMapping(String faceName, String oracleText) {
|
||||
if (!needsTranslation() || oracleMappings.containsKey(faceName)) return;
|
||||
String translatedName = getTranslatedName(faceName);
|
||||
String translatedText = getTranslatedOracle(faceName);
|
||||
if (translatedText.equals("")) {
|
||||
// english card only, fall back
|
||||
return;
|
||||
}
|
||||
String translatedName = getTranslatedName(faceName);
|
||||
List <Pair <String, String> > mapping = new ArrayList<>();
|
||||
String [] splitOracleText = oracleText.split("\\\\n");
|
||||
String [] splitTranslatedText = translatedText.split("\r\n\r\n");
|
||||
|
||||
@@ -20,6 +20,9 @@ public class ImageUtil {
|
||||
|
||||
key = key.substring(2);
|
||||
PaperCard cp = StaticData.instance().getCommonCards().getCard(key);
|
||||
if (cp == null) {
|
||||
cp = StaticData.instance().getCustomCards().getCard(key);
|
||||
}
|
||||
if (cp == null) {
|
||||
cp = StaticData.instance().getVariantCards().getCard(key);
|
||||
}
|
||||
@@ -73,6 +76,8 @@ public class ImageUtil {
|
||||
|
||||
if (includeSet) {
|
||||
String editionAliased = isDownloadUrl ? StaticData.instance().getEditions().getCode2ByCode(edition) : ImageKeys.getSetFolder(edition);
|
||||
if (editionAliased == "") //FIXME: Custom Cards Workaround
|
||||
editionAliased = edition;
|
||||
return TextUtil.concatNoSpace(editionAliased, "/", fname);
|
||||
} else {
|
||||
return fname;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.40-SNAPSHOT</version>
|
||||
<version>1.6.40</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package forge.game;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -162,15 +162,23 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean matchesValid(final Object o, final String[] valids, final Card srcCard) {
|
||||
return matchesValid(o, valids, srcCard, srcCard.getController());
|
||||
}
|
||||
|
||||
public boolean matchesValid(final Object o, final String[] valids, final Card srcCard, final Player srcPlayer) {
|
||||
if (o instanceof GameObject) {
|
||||
final GameObject c = (GameObject) o;
|
||||
return c.isValid(valids, srcCard.getController(), srcCard, this);
|
||||
return c.isValid(valids, srcPlayer, srcCard, this);
|
||||
} else if (o instanceof Iterable<?>) {
|
||||
for (Object o2 : (Iterable<?>)o) {
|
||||
if (matchesValid(o2, valids, srcCard)) {
|
||||
if (matchesValid(o2, valids, srcCard, srcPlayer)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (o instanceof String) {
|
||||
if (ArrayUtils.contains(valids, o)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -522,11 +530,16 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
|
||||
@Override
|
||||
public Map<String, String> getSVars() {
|
||||
Map<String, String> res = new HashMap<>(getSVarFallback().getSVars());
|
||||
Map<String, String> res = Maps.newHashMap(getSVarFallback().getSVars());
|
||||
res.putAll(sVars);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getDirectSVars() {
|
||||
return sVars;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSVars(Map<String, String> newSVars) {
|
||||
sVars = Maps.newTreeMap();
|
||||
|
||||
@@ -2,10 +2,12 @@ package forge.game;
|
||||
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaAtom;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardState;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
@@ -189,6 +191,25 @@ public class ForgeScript {
|
||||
if (!Expressions.compare(y, property, x)) {
|
||||
return false;
|
||||
}
|
||||
} else if (property.equals("ManaAbilityCantPaidFor")) {
|
||||
SpellAbility paidFor = sourceController.getPaidForSA();
|
||||
if (paidFor == null) {
|
||||
return false;
|
||||
}
|
||||
ManaCostBeingPaid manaCost = paidFor.getManaCostBeingPaid();
|
||||
// The following code is taken from InputPayMana.java, to determine if this mana ability can pay for SA currently being paid
|
||||
byte colorCanUse = 0;
|
||||
for (final byte color : ManaAtom.MANATYPES) {
|
||||
if (manaCost.isAnyPartPayableWith(color, sourceController.getManaPool())) {
|
||||
colorCanUse |= color;
|
||||
}
|
||||
}
|
||||
if (manaCost.isAnyPartPayableWith((byte) ManaAtom.GENERIC, sourceController.getManaPool())) {
|
||||
colorCanUse |= ManaAtom.GENERIC;
|
||||
}
|
||||
if (sa.isManaAbilityFor(paidFor, colorCanUse)) {
|
||||
return false;
|
||||
}
|
||||
} else if (sa.getHostCard() != null) {
|
||||
return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility);
|
||||
}
|
||||
|
||||
@@ -769,21 +769,31 @@ public class Game {
|
||||
p.revealFaceDownCards();
|
||||
}
|
||||
|
||||
for(Card c : cards) {
|
||||
for (Card c : cards) {
|
||||
// CR 800.4d if card is controlled by opponent, LTB should trigger
|
||||
if (c.getOwner().equals(p) && c.getController().equals(p)) {
|
||||
c.getCurrentState().clearTriggers();
|
||||
}
|
||||
}
|
||||
|
||||
for (Card c : cards) {
|
||||
if (c.getController().equals(p) && (c.isPlane() || c.isPhenomenon())) {
|
||||
planarControllerLost = true;
|
||||
}
|
||||
|
||||
if(isMultiplayer) {
|
||||
if (isMultiplayer) {
|
||||
// unattach all "Enchant Player"
|
||||
c.removeAttachedTo(p);
|
||||
if (c.getOwner().equals(p)) {
|
||||
for(Card cc : cards) {
|
||||
for (Card cc : cards) {
|
||||
cc.removeImprintedCard(c);
|
||||
cc.removeEncodedCard(c);
|
||||
cc.removeRemembered(c);
|
||||
cc.removeAttachedTo(c);
|
||||
}
|
||||
c.ceaseToExist();
|
||||
getAction().ceaseToExist(c, false);
|
||||
// CR 603.2f owner of trigger source lost game
|
||||
triggerHandler.clearDelayedTrigger(c);
|
||||
} else {
|
||||
// return stolen permanents
|
||||
if (c.getController().equals(p) && c.isInZone(ZoneType.Battlefield)) {
|
||||
@@ -822,6 +832,8 @@ public class Game {
|
||||
// Remove leftover items from
|
||||
this.getStack().removeInstancesControlledBy(p);
|
||||
|
||||
getTriggerHandler().onPlayerLost(p);
|
||||
|
||||
ingamePlayers.remove(p);
|
||||
lostPlayers.add(p);
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ public class GameAction {
|
||||
return c;
|
||||
}
|
||||
if (zoneFrom == null && !c.isToken()) {
|
||||
zoneTo.add(c, position);
|
||||
zoneTo.add(c, position, CardUtil.getLKICopy(c));
|
||||
checkStaticAbilities();
|
||||
game.getTriggerHandler().registerActiveTrigger(c, true);
|
||||
game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo));
|
||||
@@ -251,6 +251,7 @@ public class GameAction {
|
||||
copied.copyChangedTextFrom(c);
|
||||
|
||||
// copy exiled properties when adding to stack
|
||||
// will be cleanup later in MagicStack
|
||||
copied.setExiledWith(c.getExiledWith());
|
||||
copied.setExiledBy(c.getExiledBy());
|
||||
|
||||
@@ -416,13 +417,7 @@ public class GameAction {
|
||||
}
|
||||
|
||||
if (!zoneTo.is(ZoneType.Exile) && !zoneTo.is(ZoneType.Stack)) {
|
||||
Card with = c.getExiledWith();
|
||||
if (with != null) {
|
||||
with.removeUntilLeavesBattlefield(c);
|
||||
}
|
||||
|
||||
c.setExiledWith(null);
|
||||
c.setExiledBy(null);
|
||||
c.cleanupExiledWith();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,7 +471,7 @@ public class GameAction {
|
||||
if (card == c) {
|
||||
zoneTo.add(copied, position, lastKnownInfo); // the modified state of the card is also reported here (e.g. for Morbid + Awaken)
|
||||
} else {
|
||||
zoneTo.add(card, position);
|
||||
zoneTo.add(card, position, CardUtil.getLKICopy(card));
|
||||
}
|
||||
card.setZone(zoneTo);
|
||||
}
|
||||
@@ -603,8 +598,6 @@ public class GameAction {
|
||||
copied.setState(CardStateName.Original, true);
|
||||
}
|
||||
unattachCardLeavingBattlefield(copied);
|
||||
// Remove all changed keywords
|
||||
copied.removeAllChangedText(game.getNextTimestamp());
|
||||
} else if (toBattlefield) {
|
||||
// reset timestamp in changezone effects so they have same timestamp if ETB simutaneously
|
||||
copied.setTimestamp(game.getNextTimestamp());
|
||||
@@ -859,7 +852,24 @@ public class GameAction {
|
||||
}
|
||||
|
||||
public final Card exile(final Card c, SpellAbility cause) {
|
||||
return exile(c, cause, null);
|
||||
if (c == null) {
|
||||
return null;
|
||||
}
|
||||
return exile(new CardCollection(c), cause).get(0);
|
||||
}
|
||||
public final CardCollection exile(final CardCollection cards, SpellAbility cause) {
|
||||
CardZoneTable table = new CardZoneTable();
|
||||
CardCollection result = new CardCollection();
|
||||
for (Card card : cards) {
|
||||
if (cause != null) {
|
||||
table.put(card.getZone().getZoneType(), ZoneType.Exile, card);
|
||||
}
|
||||
result.add(exile(card, cause, null));
|
||||
}
|
||||
if (cause != null) {
|
||||
table.triggerChangesZoneAll(game, cause);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
public final Card exile(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
|
||||
if (game.isCardExiled(c)) {
|
||||
@@ -909,6 +919,33 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
public void ceaseToExist(Card c, boolean skipTrig) {
|
||||
c.getZone().remove(c);
|
||||
|
||||
// CR 603.6c other players LTB triggers should work
|
||||
if (!skipTrig) {
|
||||
game.addChangeZoneLKIInfo(c);
|
||||
CardCollectionView lastBattlefield = game.getLastStateBattlefield();
|
||||
int idx = lastBattlefield.indexOf(c);
|
||||
Card lki = null;
|
||||
if (idx != -1) {
|
||||
lki = lastBattlefield.get(idx);
|
||||
}
|
||||
if (lki == null) {
|
||||
lki = CardUtil.getLKICopy(c);
|
||||
}
|
||||
if (game.getCombat() != null) {
|
||||
game.getCombat().removeFromCombat(c);
|
||||
game.getCombat().saveLKI(lki);
|
||||
}
|
||||
game.getTriggerHandler().registerActiveLTBTrigger(lki);
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c);
|
||||
runParams.put(AbilityKey.CardLKI, lki);
|
||||
runParams.put(AbilityKey.Origin, c.getZone().getZoneType().name());
|
||||
game.getTriggerHandler().runTrigger(TriggerType.ChangesZone, runParams, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Temporarily disable (if mode = true) actively checking static abilities.
|
||||
private void setHoldCheckingStaticAbilities(boolean mode) {
|
||||
holdCheckingStaticAbilities = mode;
|
||||
@@ -1245,6 +1282,7 @@ public class GameAction {
|
||||
}
|
||||
|
||||
if ((game.getRules().hasAppliedVariant(GameType.Commander)
|
||||
|| game.getRules().hasAppliedVariant(GameType.Brawl)
|
||||
|| game.getRules().hasAppliedVariant(GameType.Planeswalker)) && !checkAgain) {
|
||||
Iterable<Card> cards = p.getCardsIn(ZoneType.Graveyard).threadSafeIterable();
|
||||
for (final Card c : cards) {
|
||||
@@ -1266,7 +1304,7 @@ public class GameAction {
|
||||
if (game.getCombat() != null) {
|
||||
game.getCombat().removeAbsentCombatants();
|
||||
}
|
||||
table.triggerChangesZoneAll(game);
|
||||
table.triggerChangesZoneAll(game, null);
|
||||
if (!checkAgain) {
|
||||
break; // do not continue the loop
|
||||
}
|
||||
@@ -1730,12 +1768,10 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
private void drawStartingHand(Player p1){
|
||||
|
||||
private void drawStartingHand(Player p1) {
|
||||
//check initial hand
|
||||
List<Card> lib1 = Lists.newArrayList(p1.getZone(ZoneType.Library).getCards().threadSafeIterable());
|
||||
List<Card> hand1 = lib1.subList(0,p1.getMaxHandSize());
|
||||
System.out.println(hand1.toString());
|
||||
|
||||
//shuffle
|
||||
List<Card> shuffledCards = Lists.newArrayList(p1.getZone(ZoneType.Library).getCards().threadSafeIterable());
|
||||
@@ -1743,33 +1779,32 @@ public class GameAction {
|
||||
|
||||
//check a second hand
|
||||
List<Card> hand2 = shuffledCards.subList(0,p1.getMaxHandSize());
|
||||
System.out.println(hand2.toString());
|
||||
|
||||
//choose better hand according to land count
|
||||
float averageLandRatio = getLandRatio(lib1);
|
||||
if(getHandScore(hand1, averageLandRatio)>getHandScore(hand2, averageLandRatio)){
|
||||
if (getHandScore(hand1, averageLandRatio) > getHandScore(hand2, averageLandRatio)) {
|
||||
p1.getZone(ZoneType.Library).setCards(shuffledCards);
|
||||
}
|
||||
p1.drawCards(p1.getMaxHandSize());
|
||||
}
|
||||
|
||||
private float getLandRatio(List<Card> deck){
|
||||
private float getLandRatio(List<Card> deck) {
|
||||
int landCount = 0;
|
||||
for(Card c:deck){
|
||||
if(c.isLand()){
|
||||
for (Card c:deck) {
|
||||
if (c.isLand()){
|
||||
landCount++;
|
||||
}
|
||||
}
|
||||
if (landCount == 0 ){
|
||||
if(landCount == 0) {
|
||||
return 0;
|
||||
}
|
||||
return Float.valueOf(landCount)/Float.valueOf(deck.size());
|
||||
}
|
||||
|
||||
private float getHandScore(List<Card> hand, float landRatio){
|
||||
private float getHandScore(List<Card> hand, float landRatio) {
|
||||
int landCount = 0;
|
||||
for(Card c:hand){
|
||||
if(c.isLand()){
|
||||
for (Card c:hand) {
|
||||
if (c.isLand()) {
|
||||
landCount++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,14 +41,12 @@ import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
|
||||
public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
protected final int id;
|
||||
private String name = "";
|
||||
private int preventNextDamage = 0;
|
||||
protected CardCollection attachedCards;
|
||||
protected CardCollection attachedCards = new CardCollection();
|
||||
private Map<Card, Map<String, String>> preventionShieldsWithEffects = Maps.newTreeMap();
|
||||
protected Map<CounterType, Integer> counters = Maps.newHashMap();
|
||||
|
||||
@@ -285,43 +283,36 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
public abstract boolean hasKeyword(final Keyword keyword);
|
||||
|
||||
public final CardCollectionView getEnchantedBy() {
|
||||
if (attachedCards == null) {
|
||||
return CardCollection.EMPTY;
|
||||
}
|
||||
// enchanted means attached by Aura
|
||||
return CardLists.filter(attachedCards, CardPredicates.Presets.AURA);
|
||||
return CardLists.filter(getAttachedCards(), CardPredicates.Presets.AURA);
|
||||
}
|
||||
|
||||
// doesn't include phased out cards
|
||||
public final CardCollectionView getAttachedCards() {
|
||||
return CardCollection.getView(attachedCards);
|
||||
return CardLists.filter(attachedCards, CardPredicates.phasedIn());
|
||||
}
|
||||
|
||||
// for view does include phased out cards
|
||||
public final CardCollectionView getAllAttachedCards() {
|
||||
return attachedCards;
|
||||
}
|
||||
|
||||
public final void setAttachedCards(final Iterable<Card> cards) {
|
||||
if (cards == null) {
|
||||
attachedCards = null;
|
||||
}
|
||||
else {
|
||||
attachedCards = new CardCollection(cards);
|
||||
}
|
||||
|
||||
getView().updateAttachedCards(this);
|
||||
attachedCards = new CardCollection(cards);
|
||||
updateAttachedCards();
|
||||
}
|
||||
|
||||
public final boolean hasCardAttachments() {
|
||||
return FCollection.hasElements(attachedCards);
|
||||
return !getAttachedCards().isEmpty();
|
||||
}
|
||||
|
||||
public final boolean isEnchanted() {
|
||||
if (attachedCards == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// enchanted means attached by Aura
|
||||
return Iterables.any(attachedCards, CardPredicates.Presets.AURA);
|
||||
return Iterables.any(getAttachedCards(), CardPredicates.Presets.AURA);
|
||||
}
|
||||
|
||||
public final boolean hasCardAttachment(Card c) {
|
||||
return FCollection.hasElement(attachedCards, c);
|
||||
return getAttachedCards().contains(c);
|
||||
}
|
||||
public final boolean isEnchantedBy(Card c) {
|
||||
// Rule 303.4k Even if c is no Aura it still counts
|
||||
@@ -329,9 +320,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
}
|
||||
|
||||
public final boolean hasCardAttachment(final String cardName) {
|
||||
if (attachedCards == null) {
|
||||
return false;
|
||||
}
|
||||
return CardLists.count(getAttachedCards(), CardPredicates.nameEquals(cardName)) > 0;
|
||||
}
|
||||
public final boolean isEnchantedBy(final String cardName) {
|
||||
@@ -344,12 +332,8 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
* @param Card c
|
||||
*/
|
||||
public final void addAttachedCard(final Card c) {
|
||||
if (attachedCards == null) {
|
||||
attachedCards = new CardCollection();
|
||||
}
|
||||
|
||||
if (attachedCards.add(c)) {
|
||||
getView().updateAttachedCards(this);
|
||||
updateAttachedCards();
|
||||
getGame().fireEvent(new GameEventCardAttachment(c, null, this));
|
||||
}
|
||||
}
|
||||
@@ -359,17 +343,16 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
* @param Card c
|
||||
*/
|
||||
public final void removeAttachedCard(final Card c) {
|
||||
if (attachedCards == null) { return; }
|
||||
|
||||
if (attachedCards.remove(c)) {
|
||||
if (attachedCards.isEmpty()) {
|
||||
attachedCards = null;
|
||||
}
|
||||
getView().updateAttachedCards(this);
|
||||
updateAttachedCards();
|
||||
getGame().fireEvent(new GameEventCardAttachment(c, this, null));
|
||||
}
|
||||
}
|
||||
|
||||
public final void updateAttachedCards() {
|
||||
getView().updateAttachedCards(this);
|
||||
}
|
||||
|
||||
public final void unAttachAllCards() {
|
||||
for (Card c : Lists.newArrayList(getAttachedCards())) {
|
||||
c.unattachFromEntity(this);
|
||||
|
||||
@@ -2,6 +2,9 @@ package forge.game;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.ForwardingTable;
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.Maps;
|
||||
@@ -10,49 +13,61 @@ import com.google.common.collect.Table;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.trigger.TriggerType;
|
||||
|
||||
public class GameEntityCounterTable extends ForwardingTable<GameEntity, CounterType, Integer> {
|
||||
public class GameEntityCounterTable extends ForwardingTable<Optional<Player>, GameEntity, Map<CounterType, Integer>> {
|
||||
|
||||
private Table<GameEntity, CounterType, Integer> dataMap = HashBasedTable.create();
|
||||
private Table<Optional<Player>, GameEntity, Map<CounterType, Integer>> dataMap = HashBasedTable.create();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.google.common.collect.ForwardingTable#delegate()
|
||||
*/
|
||||
@Override
|
||||
protected Table<GameEntity, CounterType, Integer> delegate() {
|
||||
protected Table<Optional<Player>, GameEntity, Map<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) {
|
||||
return super.put(rowKey, columnKey, get(rowKey, columnKey) + value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Integer get(Object rowKey, Object columnKey) {
|
||||
if (!contains(rowKey, columnKey)) {
|
||||
return 0; // helper to not return null value
|
||||
public Integer put(Player putter, GameEntity object, CounterType type, Integer value) {
|
||||
Optional<Player> o = Optional.fromNullable(putter);
|
||||
Map<CounterType, Integer> map = get(o, object);
|
||||
if (map == null) {
|
||||
map = Maps.newHashMap();
|
||||
put(o, object, map);
|
||||
}
|
||||
return super.get(rowKey, columnKey);
|
||||
return map.put(type, ObjectUtils.firstNonNull(map.get(type), 0) + value);
|
||||
}
|
||||
|
||||
public int get(Player putter, GameEntity object, CounterType type) {
|
||||
Optional<Player> o = Optional.fromNullable(putter);
|
||||
Map<CounterType, Integer> map = get(o, object);
|
||||
if (map == null || !map.containsKey(type)) {
|
||||
return 0;
|
||||
}
|
||||
return ObjectUtils.firstNonNull(map.get(type), 0);
|
||||
}
|
||||
|
||||
public int totalValues() {
|
||||
int result = 0;
|
||||
for (Map<CounterType, Integer> m : values()) {
|
||||
for (Integer i : m.values()) {
|
||||
result += i;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* returns the counters that can still be removed from game entity
|
||||
*/
|
||||
public Map<CounterType, Integer> filterToRemove(GameEntity ge) {
|
||||
Map<CounterType, Integer> result = Maps.newHashMap();
|
||||
if (!containsRow(ge)) {
|
||||
if (!containsColumn(ge)) {
|
||||
result.putAll(ge.getCounters());
|
||||
return result;
|
||||
}
|
||||
Map<CounterType, Integer> alreadyRemoved = row(ge);
|
||||
Map<CounterType, Integer> alreadyRemoved = column(ge).get(Optional.absent());
|
||||
for (Map.Entry<CounterType, Integer> e : ge.getCounters().entrySet()) {
|
||||
Integer rest = e.getValue() - (alreadyRemoved.containsKey(e.getKey()) ? alreadyRemoved.get(e.getKey()) : 0);
|
||||
if (rest > 0) {
|
||||
@@ -65,19 +80,34 @@ public class GameEntityCounterTable extends ForwardingTable<GameEntity, CounterT
|
||||
public Map<GameEntity, Integer> filterTable(CounterType type, String valid, Card host, CardTraitBase sa) {
|
||||
Map<GameEntity, Integer> result = Maps.newHashMap();
|
||||
|
||||
for (Map.Entry<GameEntity, Integer> e : column(type).entrySet()) {
|
||||
if (e.getValue() > 0 && e.getKey().isValid(valid, host.getController(), host, sa)) {
|
||||
result.put(e.getKey(), e.getValue());
|
||||
for (Map.Entry<GameEntity, Map<Optional<Player>, Map<CounterType, Integer>>> gm : columnMap().entrySet()) {
|
||||
if (gm.getKey().isValid(valid, host.getController(), host, sa)) {
|
||||
for (Map<CounterType, Integer> cm : gm.getValue().values()) {
|
||||
Integer old = ObjectUtils.firstNonNull(result.get(gm.getKey()), 0);
|
||||
Integer v = ObjectUtils.firstNonNull(cm.get(type), 0);
|
||||
result.put(gm.getKey(), old + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void triggerCountersPutAll(final Game game) {
|
||||
if (!isEmpty()) {
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Objects, this);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.CounterAddedAll, runParams, false);
|
||||
if (isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Cell<Optional<Player>, GameEntity, Map<CounterType, Integer>> c : cellSet()) {
|
||||
if (c.getValue().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Source, c.getRowKey().get());
|
||||
runParams.put(AbilityKey.Object, c.getColumnKey());
|
||||
runParams.put(AbilityKey.CounterMap, c.getValue());
|
||||
game.getTriggerHandler().runTrigger(TriggerType.CounterPlayerAddedAll, runParams, false);
|
||||
}
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Objects, this);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.CounterAddedAll, runParams, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package forge.game;
|
||||
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardView;
|
||||
import forge.trackable.TrackableCollection;
|
||||
import forge.trackable.TrackableObject;
|
||||
@@ -54,6 +55,12 @@ public abstract class GameEntityView extends TrackableObject {
|
||||
public boolean hasCardAttachments() {
|
||||
return getAttachedCards() != null;
|
||||
}
|
||||
public Iterable<CardView> getAllAttachedCards() {
|
||||
return get(TrackableProperty.AllAttachedCards);
|
||||
}
|
||||
public boolean hasAnyCardAttachments() {
|
||||
return getAllAttachedCards() != null;
|
||||
}
|
||||
|
||||
protected void updateAttachedCards(GameEntity e) {
|
||||
if (e.hasCardAttachments()) {
|
||||
@@ -62,5 +69,11 @@ public abstract class GameEntityView extends TrackableObject {
|
||||
else {
|
||||
set(TrackableProperty.AttachedCards, null);
|
||||
}
|
||||
CardCollectionView all = e.getAllAttachedCards();
|
||||
if (all.isEmpty()) {
|
||||
set(TrackableProperty.AllAttachedCards, null);
|
||||
} else {
|
||||
set(TrackableProperty.AllAttachedCards, CardView.getCollection(all));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
if (!this.allowedSetCodes_ro.isEmpty()) {
|
||||
p = Predicates.and(p, printed ?
|
||||
IPaperCard.Predicates.printedInSets(this.allowedSetCodes_ro, printed) :
|
||||
(Predicate<PaperCard>)(StaticData.instance().getCommonCards().wasPrintedInSets(this.allowedSetCodes_ro)));
|
||||
StaticData.instance().getCommonCards().wasPrintedInSets(this.allowedSetCodes_ro));
|
||||
}
|
||||
if (!this.allowedRarities.isEmpty()) {
|
||||
List<Predicate<? super PaperCard>> crp = Lists.newArrayList();
|
||||
@@ -302,6 +302,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
{
|
||||
coreFormats.add("Standard.txt");
|
||||
coreFormats.add("Pioneer.txt");
|
||||
coreFormats.add("Historic.txt");
|
||||
coreFormats.add("Modern.txt");
|
||||
coreFormats.add("Legacy.txt");
|
||||
coreFormats.add("Vintage.txt");
|
||||
@@ -484,6 +485,10 @@ public class GameFormat implements Comparable<GameFormat> {
|
||||
return this.map.get("Pioneer");
|
||||
}
|
||||
|
||||
public GameFormat getHistoric() {
|
||||
return this.map.get("Historic");
|
||||
}
|
||||
|
||||
public GameFormat getModern() {
|
||||
return this.map.get("Modern");
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ package forge.game;
|
||||
public enum GlobalRuleChange {
|
||||
|
||||
alwaysWither ("All damage is dealt as though its source had wither."),
|
||||
attackerChoosesBlockers ("The attacking player chooses how each creature blocks each turn."),
|
||||
attackerChoosesBlockers ("The attacking player chooses how each creature blocks each combat."),
|
||||
manaBurn ("A player losing unspent mana causes that player to lose that much life."),
|
||||
manapoolsDontEmpty ("Mana pools don't empty as steps and phases end."),
|
||||
noCreatureETBTriggers ("Creatures entering the battlefield don't cause abilities to trigger."),
|
||||
|
||||
@@ -15,6 +15,7 @@ public interface IHasSVars {
|
||||
//public Set<String> getSVars();
|
||||
|
||||
public Map<String, String> getSVars();
|
||||
public Map<String, String> getDirectSVars();
|
||||
|
||||
public void removeSVar(final String var);
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ public class StaticEffect {
|
||||
}
|
||||
|
||||
// remove abilities
|
||||
if (hasParam("AddAbility") || hasParam("GainsAbilitiesOf")
|
||||
if (hasParam("AddAbility") || hasParam("GainsAbilitiesOf") || hasParam("GainsAbilitiesOfDefined")
|
||||
|| hasParam("AddTrigger") || hasParam("AddStaticAbility") || hasParam("AddReplacementEffects")
|
||||
|| hasParam("RemoveAllAbilities") || hasParam("RemoveLandTypes")) {
|
||||
affectedCard.removeChangedCardTraits(getTimestamp());
|
||||
|
||||
@@ -53,6 +53,7 @@ public final class AbilityFactory {
|
||||
|
||||
static final List<String> additionalAbilityKeys = Lists.newArrayList(
|
||||
"WinSubAbility", "OtherwiseSubAbility", // Clash
|
||||
"BidSubAbility", // BidLifeEffect
|
||||
"ChooseNumberSubAbility", "Lowest", "Highest", "NotLowest", // ChooseNumber
|
||||
"HeadsSubAbility", "TailsSubAbility", "LoseSubAbility", // FlipCoin
|
||||
"TrueSubAbility", "FalseSubAbility", // Branch
|
||||
@@ -272,7 +273,7 @@ public final class AbilityFactory {
|
||||
|
||||
for (final String key : additionalAbilityKeys) {
|
||||
if (mapParams.containsKey(key) && spellAbility.getAdditionalAbility(key) == null) {
|
||||
spellAbility.setAdditionalAbility(key, getSubAbility(state, mapParams.get(key), sVarHolder));
|
||||
spellAbility.setAdditionalAbility(key, getAbility(state, mapParams.get(key), sVarHolder));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,12 +33,12 @@ public enum AbilityKey {
|
||||
Cause("Cause"),
|
||||
Causer("Causer"),
|
||||
Championed("Championed"),
|
||||
CopySA("CopySA"),
|
||||
Cost("Cost"),
|
||||
CostStack("CostStack"),
|
||||
CounterAmount("CounterAmount"),
|
||||
CounteredSA("CounteredSA"),
|
||||
CounterNum("CounterNum"),
|
||||
CounterMap("CounterMap"),
|
||||
CounterTable("CounterTable"),
|
||||
CounterType("CounterType"),
|
||||
Crew("Crew"),
|
||||
@@ -76,6 +76,7 @@ public enum AbilityKey {
|
||||
LifeGained("LifeGained"),
|
||||
Mana("Mana"),
|
||||
MergedCards("MergedCards"),
|
||||
Mode("Mode"),
|
||||
MonstrosityAmount("MonstrosityAmount"),
|
||||
NewCard("NewCard"),
|
||||
NewCounterAmount("NewCounterAmount"),
|
||||
@@ -119,6 +120,7 @@ public enum AbilityKey {
|
||||
Token("Token"),
|
||||
TokenNum("TokenNum"),
|
||||
Transformer("Transformer"),
|
||||
TriggeredParams("TriggeredParams"),
|
||||
Vehicle("Vehicle"),
|
||||
Won("Won");
|
||||
|
||||
|
||||
@@ -550,15 +550,21 @@ public class AbilityUtils {
|
||||
players.remove(game.getPhaseHandler().getPlayerTurn());
|
||||
val = CardFactoryUtil.playerXCount(players, calcX[1], card);
|
||||
}
|
||||
else if (hType.startsWith("PropertyYou") && ability instanceof SpellAbility) {
|
||||
// Hollow One
|
||||
players.add(((SpellAbility) ability).getActivatingPlayer());
|
||||
else if (hType.startsWith("PropertyYou")) {
|
||||
if (ability instanceof SpellAbility) {
|
||||
// Hollow One
|
||||
players.add(((SpellAbility) ability).getActivatingPlayer());
|
||||
} else {
|
||||
players.add(player);
|
||||
}
|
||||
val = CardFactoryUtil.playerXCount(players, calcX[1], card);
|
||||
}
|
||||
else if (hType.startsWith("Property") && ability instanceof SpellAbility) {
|
||||
else if (hType.startsWith("Property")) {
|
||||
String defined = hType.split("Property")[1];
|
||||
for (Player p : game.getPlayersInTurnOrder()) {
|
||||
if (p.hasProperty(defined, ((SpellAbility) ability).getActivatingPlayer(), ability.getHostCard(), ability)) {
|
||||
if (ability instanceof SpellAbility && p.hasProperty(defined, ((SpellAbility) ability).getActivatingPlayer(), ability.getHostCard(), ability)) {
|
||||
players.add(p);
|
||||
} else if (!(ability instanceof SpellAbility) && p.hasProperty(defined, player, ability.getHostCard(), ability)) {
|
||||
players.add(p);
|
||||
}
|
||||
}
|
||||
@@ -1395,6 +1401,14 @@ public class AbilityUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// count times ability resolves this turn
|
||||
if (!sa.isWrapper()) {
|
||||
final Card host = sa.getHostCard();
|
||||
if (host != null) {
|
||||
host.addAbilityResolved(sa);
|
||||
}
|
||||
}
|
||||
|
||||
final ApiType api = sa.getApi();
|
||||
if (api == null) {
|
||||
sa.resolve();
|
||||
@@ -1488,17 +1502,45 @@ public class AbilityUtils {
|
||||
else if (unlessCost.equals("ChosenNumber")) {
|
||||
cost = new Cost(new ManaCost(new ManaCostParser(String.valueOf(source.getChosenNumber()))), true);
|
||||
}
|
||||
else if (unlessCost.equals("RememberedCostMinus2")) {
|
||||
Card rememberedCard = (Card) source.getFirstRemembered();
|
||||
if (rememberedCard == null) {
|
||||
else if (unlessCost.startsWith("DefinedCost")) {
|
||||
CardCollection definedCards = AbilityUtils.getDefinedCards(sa.getHostCard(), unlessCost.split("_")[1], sa);
|
||||
if (definedCards.isEmpty()) {
|
||||
sa.resolve();
|
||||
resolveSubAbilities(sa, game);
|
||||
return;
|
||||
}
|
||||
ManaCostBeingPaid newCost = new ManaCostBeingPaid(rememberedCard.getManaCost());
|
||||
newCost.decreaseGenericMana(2);
|
||||
Card card = definedCards.getFirst();
|
||||
ManaCostBeingPaid newCost = new ManaCostBeingPaid(card.getManaCost());
|
||||
// Check if there's a third underscore for cost modifying
|
||||
if (unlessCost.split("_").length == 3) {
|
||||
String modifier = unlessCost.split("_")[2];
|
||||
if (modifier.startsWith("Minus")) {
|
||||
newCost.decreaseGenericMana(Integer.parseInt(modifier.substring(5)));
|
||||
} else {
|
||||
newCost.increaseGenericMana(Integer.parseInt(modifier.substring(4)));
|
||||
}
|
||||
}
|
||||
cost = new Cost(newCost.toManaCost(), true);
|
||||
}
|
||||
else if (unlessCost.startsWith("DefinedSACost")) {
|
||||
FCollection<SpellAbility> definedSAs = AbilityUtils.getDefinedSpellAbilities(sa.getHostCard(), unlessCost.split("_")[1], sa);
|
||||
if (definedSAs.isEmpty()) {
|
||||
sa.resolve();
|
||||
resolveSubAbilities(sa, game);
|
||||
return;
|
||||
}
|
||||
Card host = definedSAs.getFirst().getHostCard();
|
||||
if (host.getManaCost() == null) {
|
||||
cost = new Cost(ManaCost.ZERO, true);
|
||||
} else {
|
||||
int xCount = host.getManaCost().countX();
|
||||
int xPaid = host.getXManaCostPaid() * xCount;
|
||||
ManaCostBeingPaid toPay = new ManaCostBeingPaid(host.getManaCost());
|
||||
toPay.decreaseShard(ManaCostShard.X, xCount);
|
||||
toPay.increaseGenericMana(xPaid);
|
||||
cost = new Cost(toPay.toManaCost(), true);
|
||||
}
|
||||
}
|
||||
else if (!StringUtils.isBlank(sa.getSVar(unlessCost)) || !StringUtils.isBlank(source.getSVar(unlessCost))) {
|
||||
// check for X costs (stored in SVars
|
||||
int xCost = calculateAmount(source, TextUtil.fastReplace(sa.getParam("UnlessCost"),
|
||||
@@ -1826,6 +1868,10 @@ public class AbilityUtils {
|
||||
}
|
||||
return CardFactoryUtil.doXMath(v, expr, c);
|
||||
}
|
||||
|
||||
if (sq[0].equals("ResolvedThisTurn")) {
|
||||
return CardFactoryUtil.doXMath(sa.getResolvedThisTurn(), expr, c);
|
||||
}
|
||||
}
|
||||
|
||||
if (l[0].startsWith("CountersAddedThisTurn")) {
|
||||
@@ -1834,35 +1880,37 @@ public class AbilityUtils {
|
||||
|
||||
return CardFactoryUtil.doXMath(game.getCounterAddedThisTurn(cType, parts[2], parts[3], c, player, ctb), expr, c);
|
||||
}
|
||||
|
||||
// count valid cards in any specified zone/s
|
||||
if (l[0].startsWith("Valid")) {
|
||||
String[] lparts = l[0].split(" ", 2);
|
||||
final String[] rest = lparts[1].split(",");
|
||||
|
||||
final CardCollectionView cardsInZones = lparts[0].length() > 5
|
||||
? game.getCardsIn(ZoneType.listValueOf(lparts[0].substring(5)))
|
||||
: game.getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
CardCollection cards = CardLists.getValidCards(cardsInZones, rest, player, c, ctb);
|
||||
return CardFactoryUtil.doXMath(cards.size(), expr, c);
|
||||
}
|
||||
}
|
||||
return CardFactoryUtil.xCount(c, s2);
|
||||
}
|
||||
|
||||
public static final void applyManaColorConversion(ManaConversionMatrix matrix, final Map<String, String> params) {
|
||||
String conversionType = params.get("ManaColorConversion");
|
||||
String conversion = params.get("ManaConversion");
|
||||
|
||||
// Choices are Additives(OR) or Restrictive(AND)
|
||||
boolean additive = "Additive".equals(conversionType);
|
||||
for (String pair : conversion.split(" ")) {
|
||||
// Check if conversion is additive or restrictive and how to split
|
||||
boolean additive = pair.contains("->");
|
||||
String[] sides = pair.split(additive ? "->" : "<-");
|
||||
|
||||
for(String c : MagicColor.Constant.COLORS_AND_COLORLESS) {
|
||||
// Use the strings from MagicColor, since that's how the Script will be coming in as
|
||||
String key = StringUtils.capitalize(c) + "Conversion";
|
||||
if (params.containsKey(key)) {
|
||||
String convertTo = params.get(key);
|
||||
byte convertByte = 0;
|
||||
if ("Type".equals(convertTo)) {
|
||||
// IMPORTANT! We need to use Mana Color here not Card Color.
|
||||
convertByte = ManaAtom.ALL_MANA_TYPES;
|
||||
} else if ("Color".equals(convertTo)) {
|
||||
// IMPORTANT! We need to use Mana Color here not Card Color.
|
||||
convertByte = ManaAtom.ALL_MANA_COLORS;
|
||||
} else {
|
||||
for (final String convertColor : convertTo.split(",")) {
|
||||
convertByte |= ManaAtom.fromName(convertColor);
|
||||
}
|
||||
if (sides[0].equals("AnyColor") || sides[0].equals("AnyType")) {
|
||||
for (byte c : (sides[0].equals("AnyColor") ? MagicColor.WUBRG : MagicColor.WUBRGC)) {
|
||||
matrix.adjustColorReplacement(c, ManaAtom.fromConversion(sides[1]), additive);
|
||||
}
|
||||
// AdjustColorReplacement has two different matrices handling final mana conversion under the covers
|
||||
matrix.adjustColorReplacement(ManaAtom.fromName(c), convertByte, additive);
|
||||
} else {
|
||||
matrix.adjustColorReplacement(ManaAtom.fromConversion(sides[0]), ManaAtom.fromConversion(sides[1]), additive);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2010,19 +2058,6 @@ public class AbilityUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static SpellAbility getCause(SpellAbility sa) {
|
||||
final SpellAbility root = sa.getRootAbility();
|
||||
SpellAbility cause = sa;
|
||||
if (root.isReplacementAbility()) {
|
||||
SpellAbility replacingObject = (SpellAbility) root.getReplacingObject(AbilityKey.Cause);
|
||||
if (replacingObject != null) {
|
||||
cause = replacingObject;
|
||||
}
|
||||
}
|
||||
return cause;
|
||||
}
|
||||
|
||||
|
||||
public static SpellAbility addSpliceEffects(final SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Player player = sa.getActivatingPlayer();
|
||||
|
||||
@@ -30,6 +30,7 @@ public enum ApiType {
|
||||
Block (BlockEffect.class),
|
||||
Bond (BondEffect.class),
|
||||
Branch (BranchEffect.class),
|
||||
Camouflage (CamouflageEffect.class),
|
||||
ChangeCombatants (ChangeCombatantsEffect.class),
|
||||
ChangeTargets (ChangeTargetsEffect.class),
|
||||
ChangeText (ChangeTextEffect.class),
|
||||
@@ -92,6 +93,7 @@ public enum ApiType {
|
||||
Haunt (HauntEffect.class),
|
||||
Investigate (InvestigateEffect.class),
|
||||
ImmediateTrigger (ImmediateTriggerEffect.class),
|
||||
Learn (LearnEffect.class),
|
||||
LookAt (LookAtEffect.class),
|
||||
LoseLife (LifeLoseEffect.class),
|
||||
LosesGame (GameLossEffect.class),
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.game.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -19,6 +20,7 @@ import forge.game.GameEntity;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.combat.Combat;
|
||||
@@ -634,10 +636,19 @@ public abstract class SpellAbilityEffect {
|
||||
@Override
|
||||
public void run() {
|
||||
CardZoneTable untilTable = new CardZoneTable();
|
||||
CardCollectionView untilCards = hostCard.getUntilLeavesBattlefield();
|
||||
// if the list is empty, then the table doesn't need to be checked anymore
|
||||
if (untilCards.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Table.Cell<ZoneType, ZoneType, CardCollection> cell : triggerList.cellSet()) {
|
||||
for (Card c : cell.getValue()) {
|
||||
// check if card is still in the until host leaves play list
|
||||
if (!untilCards.contains(c)) {
|
||||
continue;
|
||||
}
|
||||
// better check if card didn't changed zones again?
|
||||
Card newCard = c.getZone().getCards().get(c);
|
||||
Card newCard = game.getCardState(c, null);
|
||||
if (newCard == null || !newCard.equalsWithTimestamp(c)) {
|
||||
continue;
|
||||
}
|
||||
@@ -646,9 +657,44 @@ public abstract class SpellAbilityEffect {
|
||||
untilTable.put(cell.getColumnKey(), cell.getRowKey(), movedCard);
|
||||
}
|
||||
}
|
||||
untilTable.triggerChangesZoneAll(game);
|
||||
untilTable.triggerChangesZoneAll(game, null);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
protected static void discard(SpellAbility sa, CardZoneTable table, Map<Player, CardCollectionView> discardedMap) {
|
||||
Set<Player> discarders = discardedMap.keySet();
|
||||
for (Player p : discarders) {
|
||||
final CardCollection discardedByPlayer = new CardCollection();
|
||||
for (Card card : Lists.newArrayList(discardedMap.get(p))) { // without copying will get concurrent modification exception
|
||||
if (card == null) { continue; }
|
||||
if (p.discard(card, sa, table) != null) {
|
||||
discardedByPlayer.add(card);
|
||||
|
||||
if (sa.hasParam("RememberDiscarded")) {
|
||||
sa.getHostCard().addRemembered(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
discardedMap.put(p, discardedByPlayer);
|
||||
}
|
||||
|
||||
for (Player p : discarders) {
|
||||
CardCollectionView discardedByPlayer = discardedMap.get(p);
|
||||
if (!discardedByPlayer.isEmpty()) {
|
||||
boolean firstDiscard = p.getNumDiscardedThisTurn() - discardedByPlayer.size() == 0;
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Player, p);
|
||||
runParams.put(AbilityKey.Cards, discardedByPlayer);
|
||||
runParams.put(AbilityKey.Cause, sa);
|
||||
runParams.put(AbilityKey.FirstTime, firstDiscard);
|
||||
p.getGame().getTriggerHandler().runTrigger(TriggerType.DiscardedAll, runParams, false);
|
||||
|
||||
if (sa.hasParam("RememberDiscardingPlayers")) {
|
||||
sa.getHostCard().addRemembered(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class AmassEffect extends TokenEffectBase {
|
||||
makeTokens(prototype, activator, sa, 1, true, false, triggerList, combatChanged);
|
||||
|
||||
if (!useZoneTable) {
|
||||
triggerList.triggerChangesZoneAll(game);
|
||||
triggerList.triggerChangesZoneAll(game, sa);
|
||||
triggerList.clear();
|
||||
}
|
||||
game.fireEvent(new GameEventTokenCreated());
|
||||
|
||||
@@ -12,6 +12,8 @@ import forge.game.card.Card;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.event.GameEventCardStatsChanged;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Lang;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
|
||||
public class AnimateEffect extends AnimateEffectBase {
|
||||
@@ -139,6 +141,17 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
|
||||
List<Card> tgts = getCardsfromTargets(sa);
|
||||
|
||||
if (sa.hasParam("Optional")) {
|
||||
final String targets = Lang.joinHomogenous(tgts);
|
||||
final String message = sa.hasParam("OptionQuestion")
|
||||
? TextUtil.fastReplace(sa.getParam("OptionQuestion"), "TARGETS", targets)
|
||||
: getStackDescription(sa);
|
||||
|
||||
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (final Card c : tgts) {
|
||||
doAnimate(c, sa, power, toughness, types, removeTypes, finalDesc,
|
||||
keywords, removeKeywords, hiddenKeywords,
|
||||
@@ -229,9 +242,7 @@ public class AnimateEffect extends AnimateEffectBase {
|
||||
|
||||
final List<Card> tgts = getCardsfromTargets(sa);
|
||||
|
||||
for (final Card c : tgts) {
|
||||
sb.append(c).append(" ");
|
||||
}
|
||||
sb.append(Lang.joinHomogenous(tgts)).append(" ");
|
||||
|
||||
// if power is -1, we'll assume it's not just setting toughness
|
||||
if (power != null && toughness != null) {
|
||||
|
||||
@@ -43,7 +43,7 @@ public class AttachEffect extends SpellAbilityEffect {
|
||||
if (newZone != previousZone) {
|
||||
table.put(previousZone, newZone, c);
|
||||
}
|
||||
table.triggerChangesZoneAll(game);
|
||||
table.triggerChangesZoneAll(game, sa);
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user