mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28: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 |
80
README.md
80
README.md
@@ -1,15 +1,15 @@
|
|||||||
# Forge
|
# 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)
|
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)
|
Discord channel [here](https://discordapp.com/channels/267367946135928833/267742313390931968)
|
||||||
|
|
||||||
# Requirements / Tools
|
## Requirements / Tools
|
||||||
|
|
||||||
- Java IDE such as IntelliJ or Eclipse
|
- you favourite Java IDE (IntelliJ, Eclipse, VSCodium, Emacs, Vi...)
|
||||||
- Java JDK 8 or later
|
- Java JDK 8 or later (some IDEs such as Eclipse require JDK11+, whereas the Android build currently only works with JDK8)
|
||||||
- Git
|
- Git
|
||||||
- Git client (optional)
|
- Git client (optional)
|
||||||
- Maven
|
- Maven
|
||||||
@@ -18,7 +18,7 @@ Discord channel [here](https://discordapp.com/channels/267367946135928833/267742
|
|||||||
- Android SDK (optional: for Android releases)
|
- Android SDK (optional: for Android releases)
|
||||||
- RoboVM (optional: for iOS releases) (TBD: Current status of support by libgdx)
|
- 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.
|
- Log in to gitlab with your user account and fork the project.
|
||||||
|
|
||||||
@@ -26,11 +26,11 @@ 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`
|
- 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.
|
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.
|
||||||
|
|
||||||
@@ -55,9 +55,9 @@ Eclipse includes Maven integration so a separate install is not necessary. For
|
|||||||
|
|
||||||
- Once everything builds, all errors should disappear. You can now advance to Project launch.
|
- 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.
|
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!
|
- 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.
|
This is the configuration used for doing mobile development using the Windows / Linux / MacOS front-end. Knowledge of libgdx is helpful here.
|
||||||
|
|
||||||
@@ -73,24 +73,24 @@ This is the configuration used for doing mobile development using the Windows /
|
|||||||
|
|
||||||
- A view similar to a mobile phone should appear. Enjoy!
|
- 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.
|
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
|
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
|
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.
|
in the following instructions as your 'Android SDK Install' path.
|
||||||
|
|
||||||
#### Linux / Mac OSX
|
##### Linux / Mac OSX
|
||||||
|
|
||||||
TBD
|
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
|
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
|
from: https://github.com/khaledev/ADT/releases
|
||||||
@@ -98,25 +98,24 @@ 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
|
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.
|
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:
|
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 SDK Build-tools 26.0.1
|
||||||
- Android 7.1.1 (API 25) SDK Platform
|
- Android 8.0.0 (API 26) SDK Platform
|
||||||
- Google USB Driver 11
|
- 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.
|
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
|
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.
|
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.
|
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.
|
You'll need to have the Android SDK install path platform-tools/ path in your command search path to easily deploy builds.
|
||||||
|
|
||||||
@@ -149,14 +148,14 @@ 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`
|
- Install the new apk: `adb install forge-android-[version].apk`
|
||||||
|
|
||||||
### Android Debugging
|
#### Android Debugging
|
||||||
|
|
||||||
Assuming the apk is installed, launch it from the device.
|
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.
|
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.
|
SNAPSHOT builds can be built via the Maven integration in Eclipse.
|
||||||
|
|
||||||
@@ -167,19 +166,19 @@ SNAPSHOT builds can be built via the Maven integration in Eclipse.
|
|||||||
|
|
||||||
The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desktop-[version]-SNAPSHOT
|
The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desktop-[version]-SNAPSHOT
|
||||||
|
|
||||||
# IntelliJ
|
## IntelliJ
|
||||||
|
|
||||||
Quick start guide for [setting up the Forge project within IntelliJ](https://git.cardforge.org/core-developers/forge/wikis/intellij-setup).
|
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.
|
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.
|
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:
|
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
|
||||||
- forge-gui-mobile-dev
|
- 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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>1.6.40-SNAPSHOT</version>
|
<version>1.6.40</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-ai</artifactId>
|
<artifactId>forge-ai</artifactId>
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public class AiAttackController {
|
|||||||
|
|
||||||
public AiAttackController(final Player ai, boolean nextTurn) {
|
public AiAttackController(final Player ai, boolean nextTurn) {
|
||||||
this.ai = ai;
|
this.ai = ai;
|
||||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
this.defendingOpponent = choosePreferredDefenderPlayer(ai);
|
||||||
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||||
this.myList = ai.getCreaturesInPlay();
|
this.myList = ai.getCreaturesInPlay();
|
||||||
this.attackers = new ArrayList<>();
|
this.attackers = new ArrayList<>();
|
||||||
@@ -107,7 +107,7 @@ public class AiAttackController {
|
|||||||
|
|
||||||
public AiAttackController(final Player ai, Card attacker) {
|
public AiAttackController(final Player ai, Card attacker) {
|
||||||
this.ai = ai;
|
this.ai = ai;
|
||||||
this.defendingOpponent = choosePreferredDefenderPlayer();
|
this.defendingOpponent = choosePreferredDefenderPlayer(ai);
|
||||||
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
this.oppList = getOpponentCreatures(this.defendingOpponent);
|
||||||
this.myList = ai.getCreaturesInPlay();
|
this.myList = ai.getCreaturesInPlay();
|
||||||
this.attackers = new ArrayList<>();
|
this.attackers = new ArrayList<>();
|
||||||
@@ -156,13 +156,12 @@ public class AiAttackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Choose opponent for AI to attack here. Expand as necessary. */
|
/** Choose opponent for AI to attack here. Expand as necessary. */
|
||||||
private Player choosePreferredDefenderPlayer() {
|
public static Player choosePreferredDefenderPlayer(Player ai) {
|
||||||
Player defender = ai.getWeakestOpponent(); //Gets opponent with the least life
|
Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range
|
||||||
|
|
||||||
if (defender.getLife() < 8) { //Concentrate on opponent within easy kill range
|
if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players
|
||||||
return defender;
|
// TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked
|
||||||
} else { //Otherwise choose a random opponent to ensure no ganging up on players
|
return ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
|
||||||
defender = ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
|
|
||||||
}
|
}
|
||||||
return defender;
|
return defender;
|
||||||
}
|
}
|
||||||
@@ -624,7 +623,7 @@ public class AiAttackController {
|
|||||||
int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage;
|
int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage;
|
||||||
int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, opp);
|
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)) {
|
&& !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && opp.getLife() < 1)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -919,7 +918,7 @@ public class AiAttackController {
|
|||||||
// find the potential damage ratio the AI can cause
|
// find the potential damage ratio the AI can cause
|
||||||
double humanLifeToDamageRatio = 1000000;
|
double humanLifeToDamageRatio = 1000000;
|
||||||
if (candidateUnblockedDamage > 0) {
|
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
|
// 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.ChangeZoneAi;
|
||||||
import forge.ai.ability.ExploreAi;
|
import forge.ai.ability.ExploreAi;
|
||||||
|
import forge.ai.ability.LearnAi;
|
||||||
import forge.ai.simulation.SpellAbilityPicker;
|
import forge.ai.simulation.SpellAbilityPicker;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
@@ -88,6 +89,7 @@ import forge.game.spellability.SpellAbilityCondition;
|
|||||||
import forge.game.spellability.SpellAbilityPredicates;
|
import forge.game.spellability.SpellAbilityPredicates;
|
||||||
import forge.game.spellability.SpellPermanent;
|
import forge.game.spellability.SpellPermanent;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.trigger.WrappedAbility;
|
import forge.game.trigger.WrappedAbility;
|
||||||
@@ -826,8 +828,11 @@ public class AiController {
|
|||||||
}
|
}
|
||||||
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
|
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
|
||||||
}
|
}
|
||||||
if (sa.usesTargeting() && !sa.isTargetNumberValid()) {
|
if (sa.usesTargeting()) {
|
||||||
if (!sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
if (!sa.isTargetNumberValid() && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||||
|
return AiPlayDecision.TargetingFailed;
|
||||||
|
}
|
||||||
|
if (!StaticAbilityMustTarget.meetsMustTargetRestriction(sa)) {
|
||||||
return AiPlayDecision.TargetingFailed;
|
return AiPlayDecision.TargetingFailed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1110,12 +1115,18 @@ public class AiController {
|
|||||||
final CardCollection discardList = new CardCollection();
|
final CardCollection discardList = new CardCollection();
|
||||||
int count = 0;
|
int count = 0;
|
||||||
if (sa != null) {
|
if (sa != null) {
|
||||||
|
String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
sourceCard = sa.getHostCard();
|
sourceCard = sa.getHostCard();
|
||||||
if ("Always".equals(sa.getParam("AILogic")) && !validCards.isEmpty()) {
|
if ("Always".equals(logic) && !validCards.isEmpty()) {
|
||||||
min = 1;
|
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);
|
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
|
||||||
} else if ("DiscardCMCX".equals(sa.getParam("AILogic"))) {
|
} else if ("DiscardCMCX".equals(logic)) {
|
||||||
final int cmc = sa.getXManaCostPaid();
|
final int cmc = sa.getXManaCostPaid();
|
||||||
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc));
|
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc));
|
||||||
if (discards.isEmpty()) {
|
if (discards.isEmpty()) {
|
||||||
@@ -2089,8 +2100,11 @@ public class AiController {
|
|||||||
if (useSimulation) {
|
if (useSimulation) {
|
||||||
return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa.getApi() == ApiType.Explore) {
|
if (sa.getApi() == ApiType.Explore) {
|
||||||
return ExploreAi.shouldPutInGraveyard(fetchList, decider);
|
return ExploreAi.shouldPutInGraveyard(fetchList, decider);
|
||||||
|
} else if (sa.getApi() == ApiType.Learn) {
|
||||||
|
return LearnAi.chooseCardToLearn(fetchList, decider, sa);
|
||||||
} else {
|
} else {
|
||||||
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import forge.game.cost.*;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
@@ -23,36 +24,6 @@ import forge.game.card.CardLists;
|
|||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CounterEnumType;
|
import forge.game.card.CounterEnumType;
|
||||||
import forge.game.card.CounterType;
|
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.keyword.Keyword;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -577,6 +548,11 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
|
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) {
|
protected int removeCounter(GameEntityCounterTable table, List<Card> prefs, CounterEnumType cType, int stillToRemove) {
|
||||||
int removed = 0;
|
int removed = 0;
|
||||||
if (!prefs.isEmpty() && stillToRemove > 0) {
|
if (!prefs.isEmpty() && stillToRemove > 0) {
|
||||||
@@ -590,7 +566,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
|||||||
int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove);
|
int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove);
|
||||||
if (thisRemove > 0) {
|
if (thisRemove > 0) {
|
||||||
removed += thisRemove;
|
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);
|
int thisRemove = Math.min(card.getCounters(ctype), c - toRemove);
|
||||||
if (thisRemove > 0) {
|
if (thisRemove > 0) {
|
||||||
toRemove += thisRemove;
|
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);
|
int over = Math.min(e.getValue(), c - toRemove);
|
||||||
if (over > 0) {
|
if (over > 0) {
|
||||||
toRemove += over;
|
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);
|
int over = Math.min(e.getValue(), c - toRemove);
|
||||||
if (over > 0) {
|
if (over > 0) {
|
||||||
toRemove += over;
|
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);
|
int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove);
|
||||||
if (over > 0) {
|
if (over > 0) {
|
||||||
toRemove += over;
|
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);
|
int thisRemove = Math.min(card.getCounters(cost.counter), c - toRemove);
|
||||||
if (thisRemove > 0) {
|
if (thisRemove > 0) {
|
||||||
toRemove += thisRemove;
|
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);
|
int thisRemove = Math.min(e.getValue(), c - toRemove);
|
||||||
if (thisRemove > 0) {
|
if (thisRemove > 0) {
|
||||||
toRemove += thisRemove;
|
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 Card source = sa.getHostCard();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
|
||||||
|
|
||||||
// Play higher costing spells first?
|
// Play higher costing spells first?
|
||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
|
|
||||||
@@ -213,7 +212,8 @@ public class ComputerUtil {
|
|||||||
if (unless != null && !unless.endsWith(">")) {
|
if (unless != null && !unless.endsWith(">")) {
|
||||||
final int amount = AbilityUtils.calculateAmount(source, unless, sa);
|
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 the Unless isn't enough, this should be less likely to be used
|
||||||
if (amount > usableManaSources) {
|
if (amount > usableManaSources) {
|
||||||
@@ -1068,9 +1068,6 @@ public class ComputerUtil {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (card.isEquipment() && buffedcard.isCreature() && CombatUtil.canAttack(buffedcard, ComputerUtil.getOpponentFor(ai))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (card.isCreature()) {
|
if (card.isCreature()) {
|
||||||
if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
|
if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
|
||||||
return true;
|
return true;
|
||||||
@@ -1093,8 +1090,8 @@ public class ComputerUtil {
|
|||||||
|
|
||||||
} // BuffedBy
|
} // BuffedBy
|
||||||
|
|
||||||
// get all cards the human controls with AntiBuffedBy
|
// there's a good chance AI will attack weak target
|
||||||
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
|
||||||
for (Card buffedcard : antibuffed) {
|
for (Card buffedcard : antibuffed) {
|
||||||
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
||||||
final String buffedby = buffedcard.getSVar("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
|
* @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) {
|
public static boolean shouldCastLessThanMax(final Player ai, final Card source) {
|
||||||
boolean ret = true;
|
if (source.getXManaCostPaid() > 0) {
|
||||||
if (source.getManaCost().countX() > 0) {
|
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by payment resources available.
|
||||||
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available.
|
return true;
|
||||||
return ret;
|
}
|
||||||
} else {
|
if (aiLifeInDanger(ai, false, 0)) {
|
||||||
// Otherwise, if life is possibly in danger, then this is fine.
|
// Otherwise, if life is possibly in danger, then this is fine.
|
||||||
Combat combat = new Combat(ComputerUtil.getOpponentFor(ai));
|
return true;
|
||||||
CardCollectionView attackers = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
|
|
||||||
for (Card att : attackers) {
|
|
||||||
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
|
|
||||||
combat.addAttacker(att, ComputerUtil.getOpponentFor(att.getController()));
|
|
||||||
}
|
}
|
||||||
}
|
// do not play now.
|
||||||
AiBlockController aiBlock = new AiBlockController(ai);
|
return false;
|
||||||
aiBlock.assignBlockersForCombat(combat);
|
|
||||||
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
|
||||||
// Otherwise, return false. Do not play now.
|
|
||||||
ret = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1266,8 +1252,8 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all cards the human controls with AntiBuffedBy
|
// there's a good chance AI will attack weak target
|
||||||
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
|
final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
|
||||||
for (Card buffedcard : antibuffed) {
|
for (Card buffedcard : antibuffed) {
|
||||||
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
if (buffedcard.hasSVar("AntiBuffedBy")) {
|
||||||
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
|
||||||
@@ -1463,7 +1449,7 @@ public class ComputerUtil {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int possibleNonCombatDamage(Player ai) {
|
public static int possibleNonCombatDamage(Player ai, Player enemy) {
|
||||||
int damage = 0;
|
int damage = 0;
|
||||||
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
|
||||||
all.addAll(ai.getCardsActivableInExternalZones(true));
|
all.addAll(ai.getCardsActivableInExternalZones(true));
|
||||||
@@ -1483,7 +1469,6 @@ public class ComputerUtil {
|
|||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final Player enemy = ComputerUtil.getOpponentFor(ai);
|
|
||||||
if (!sa.canTarget(enemy)) {
|
if (!sa.canTarget(enemy)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -2346,7 +2331,7 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (logic.equals("ChosenLandwalk")) {
|
else if (logic.equals("ChosenLandwalk")) {
|
||||||
for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) {
|
for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
|
||||||
for (String t : c.getType()) {
|
for (String t : c.getType()) {
|
||||||
if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) {
|
if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) {
|
||||||
chosen = t;
|
chosen = t;
|
||||||
@@ -2364,7 +2349,7 @@ public class ComputerUtil {
|
|||||||
else if (kindOfType.equals("Land")) {
|
else if (kindOfType.equals("Land")) {
|
||||||
if (logic != null) {
|
if (logic != null) {
|
||||||
if (logic.equals("ChosenLandwalk")) {
|
if (logic.equals("ChosenLandwalk")) {
|
||||||
for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) {
|
for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
|
||||||
for (String t : c.getType().getLandTypes()) {
|
for (String t : c.getType().getLandTypes()) {
|
||||||
if (!invalidTypes.contains(t)) {
|
if (!invalidTypes.contains(t)) {
|
||||||
chosen = t;
|
chosen = t;
|
||||||
@@ -2399,15 +2384,18 @@ public class ComputerUtil {
|
|||||||
case "Torture":
|
case "Torture":
|
||||||
return "Torture";
|
return "Torture";
|
||||||
case "GraceOrCondemnation":
|
case "GraceOrCondemnation":
|
||||||
return ai.getCreaturesInPlay().size() > ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size() ? "Grace"
|
List<ZoneType> graceZones = new ArrayList<ZoneType>();
|
||||||
: "Condemnation";
|
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":
|
case "CarnageOrHomage":
|
||||||
CardCollection cardsInPlay = CardLists
|
CardCollection cardsInPlay = CardLists.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
|
||||||
.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
|
|
||||||
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
|
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
|
||||||
CardCollection computerlist = CardLists.filterControlledBy(cardsInPlay, ai);
|
CardCollection computerlist = ai.getCreaturesInPlay();
|
||||||
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard
|
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
|
||||||
.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
|
|
||||||
case "Judgment":
|
case "Judgment":
|
||||||
if (votes.isEmpty()) {
|
if (votes.isEmpty()) {
|
||||||
CardCollection list = new CardCollection();
|
CardCollection list = new CardCollection();
|
||||||
@@ -2934,23 +2922,6 @@ public class ComputerUtil {
|
|||||||
return true;
|
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) {
|
public static int countUsefulCreatures(Player p) {
|
||||||
CardCollection creats = p.getCreaturesInPlay();
|
CardCollection creats = p.getCreaturesInPlay();
|
||||||
int count = 0;
|
int count = 0;
|
||||||
@@ -3033,7 +3004,7 @@ public class ComputerUtil {
|
|||||||
// call this to determine if it's safe to use a life payment spell
|
// 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.
|
// or trigger "emergency" strategies such as holding mana for Spike Weaver of Counterspell.
|
||||||
public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) {
|
public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) {
|
||||||
Player opponent = ComputerUtil.getOpponentFor(ai);
|
for (Player opponent: ai.getOpponents()) {
|
||||||
// test whether the human can kill the ai next turn
|
// test whether the human can kill the ai next turn
|
||||||
Combat combat = new Combat(opponent);
|
Combat combat = new Combat(opponent);
|
||||||
boolean containsAttacker = false;
|
boolean containsAttacker = false;
|
||||||
@@ -3059,6 +3030,7 @@ public class ComputerUtil {
|
|||||||
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
|
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -550,7 +550,7 @@ public class ComputerUtilCard {
|
|||||||
*/
|
*/
|
||||||
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
|
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
|
||||||
AiBlockController aiBlk = new AiBlockController(ai);
|
AiBlockController aiBlk = new AiBlockController(ai);
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
Combat combat = new Combat(opp);
|
Combat combat = new Combat(opp);
|
||||||
//Use actual attackers if available, else consider all possible attackers
|
//Use actual attackers if available, else consider all possible attackers
|
||||||
Combat currentCombat = ai.getGame().getCombat();
|
Combat currentCombat = ai.getGame().getCombat();
|
||||||
|
|||||||
@@ -97,34 +97,39 @@ public class ComputerUtilCombat {
|
|||||||
* canAttackNextTurn.
|
* canAttackNextTurn.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param atacker
|
* @param attacker
|
||||||
* a {@link forge.game.card.Card} object.
|
* a {@link forge.game.card.Card} object.
|
||||||
* @param defender
|
* @param defender
|
||||||
* the defending {@link GameEntity}.
|
* the defending {@link GameEntity}.
|
||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean canAttackNextTurn(final Card atacker, final GameEntity defender) {
|
public static boolean canAttackNextTurn(final Card attacker, final GameEntity defender) {
|
||||||
if (!atacker.isCreature()) {
|
if (!attacker.isCreature()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!CombatUtil.canAttackNextTurn(atacker, defender)) {
|
if (!CombatUtil.canAttackNextTurn(attacker, defender)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final KeywordInterface inst : atacker.getKeywords()) {
|
for (final KeywordInterface inst : attacker.getKeywords()) {
|
||||||
final String keyword = inst.getOriginal();
|
final String keyword = inst.getOriginal();
|
||||||
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
|
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
|
||||||
final String defined = keyword.split(":")[1];
|
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)) {
|
if (!defender.equals(player)) {
|
||||||
return false;
|
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
|
// The creature won't untap next turn
|
||||||
return !atacker.isTapped() || Untap.canUntap(atacker);
|
return !attacker.isTapped() || Untap.canUntap(attacker);
|
||||||
} // canAttackNextTurn(Card, GameEntity)
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -612,7 +612,7 @@ public class ComputerUtilCost {
|
|||||||
// if payer can't lose life its no need to pay unless
|
// if payer can't lose life its no need to pay unless
|
||||||
if (!payer.canLoseLife())
|
if (!payer.canLoseLife())
|
||||||
return false;
|
return false;
|
||||||
else if (payer.getLife() <= Integer.valueOf(aiLogic.substring(6))) {
|
else if (payer.getLife() <= AbilityUtils.calculateAmount(source, aiLogic.substring(6), sa)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if ("WillAttack".equals(aiLogic)) {
|
} else if ("WillAttack".equals(aiLogic)) {
|
||||||
@@ -684,14 +684,14 @@ public class ComputerUtilCost {
|
|||||||
public static int getMaxXValue(SpellAbility sa, Player ai) {
|
public static int getMaxXValue(SpellAbility sa, Player ai) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final SpellAbility root = sa.getRootAbility();
|
final SpellAbility root = sa.getRootAbility();
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = root.getPayCosts();
|
||||||
if (abCost == null || !abCost.hasXInAnyCostPart()) {
|
if (abCost == null || !abCost.hasXInAnyCostPart()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Integer val = null;
|
Integer val = null;
|
||||||
|
|
||||||
if (sa.costHasManaX()) {
|
if (root.costHasManaX()) {
|
||||||
val = ComputerUtilMana.determineLeftoverMana(root, ai);
|
val = ComputerUtilMana.determineLeftoverMana(root, ai);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ public class ComputerUtilMana {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts)) {
|
if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts, cost.getXManaCostPaidByColor())) {
|
||||||
return paymentChoice;
|
return paymentChoice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -547,7 +547,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get a mana of this type from floating, bail if none available
|
// 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 (mana != null) {
|
||||||
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, false)) {
|
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, false)) {
|
||||||
manaSpentToPay.add(0, mana);
|
manaSpentToPay.add(0, mana);
|
||||||
@@ -936,7 +936,7 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get a mana of this type from floating, bail if none available
|
// 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 (mana != null) {
|
||||||
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, test)) {
|
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, test)) {
|
||||||
manaSpentToPay.add(0, mana);
|
manaSpentToPay.add(0, mana);
|
||||||
@@ -965,8 +965,10 @@ public class ComputerUtilMana {
|
|||||||
* a {@link forge.game.spellability.SpellAbility} object.
|
* a {@link forge.game.spellability.SpellAbility} object.
|
||||||
* @return a {@link forge.game.mana.Mana} 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) {
|
private static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor,
|
||||||
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard, saBeingPaidFor, restriction, colorsPaid);
|
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
|
// Exclude border case
|
||||||
if (weightedOptions.isEmpty()) {
|
if (weightedOptions.isEmpty()) {
|
||||||
@@ -1015,9 +1017,13 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static List<Pair<Mana, Integer>> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard,
|
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<>();
|
final List<Pair<Mana, Integer>> weightedOptions = new ArrayList<>();
|
||||||
for (final Mana thisMana : manapool) {
|
for (final Mana thisMana : manapool) {
|
||||||
|
if (shard == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(MagicColor.toShortString(thisMana.getColor()), xManaCostPaidByColor)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
|
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
|
||||||
continue;
|
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();
|
final Card sourceCard = ma.getHostCard();
|
||||||
|
|
||||||
if (isManaSourceReserved(ai, sourceCard, sa)) {
|
if (isManaSourceReserved(ai, sourceCard, sa)) {
|
||||||
@@ -1131,6 +1137,10 @@ public class ComputerUtilMana {
|
|||||||
|
|
||||||
if (m.isComboMana()) {
|
if (m.isComboMana()) {
|
||||||
for (String s : m.getComboColors().split(" ")) {
|
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)))
|
if ("Any".equals(s) || ai.getManaPool().canPayForShardWithColor(toPay, ManaAtom.fromName(s)))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1141,6 +1151,9 @@ public class ComputerUtilMana {
|
|||||||
Set<String> reflected = CardUtil.getReflectableManaColors(ma);
|
Set<String> reflected = CardUtil.getReflectableManaColors(ma);
|
||||||
|
|
||||||
for (byte c : MagicColor.WUBRG) {
|
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))) {
|
if (ai.getManaPool().canPayForShardWithColor(toPay, c) && reflected.contains(MagicColor.toLongString(c))) {
|
||||||
m.setExpressChoice(MagicColor.toShortString(c));
|
m.setExpressChoice(MagicColor.toShortString(c));
|
||||||
return true;
|
return true;
|
||||||
@@ -1148,6 +1161,16 @@ public class ComputerUtilMana {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (toPay == ManaCostShard.COLORED_X) {
|
||||||
|
for (String s : m.mana().split(" ")) {
|
||||||
|
if (ManaCostBeingPaid.canColoredXShardBePaidByColor(s, xManaCostPaidByColor)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1434,17 +1457,26 @@ public class ComputerUtilMana {
|
|||||||
// Tack xMana Payments into mana here if X is a set value
|
// Tack xMana Payments into mana here if X is a set value
|
||||||
if (cost.getXcounter() > 0 || extraMana > 0) {
|
if (cost.getXcounter() > 0 || extraMana > 0) {
|
||||||
int manaToAdd = 0;
|
int manaToAdd = 0;
|
||||||
|
int xCounter = cost.getXcounter();
|
||||||
if (test && extraMana > 0) {
|
if (test && extraMana > 0) {
|
||||||
final int multiplicator = Math.max(cost.getXcounter(), 1);
|
final int multiplicator = Math.max(xCounter, 1);
|
||||||
manaToAdd = extraMana * multiplicator;
|
manaToAdd = extraMana * multiplicator;
|
||||||
} else {
|
} 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) {
|
if (!test) {
|
||||||
sa.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
sa.setXManaCostPaid(manaToAdd / xCounter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1531,7 +1563,7 @@ public class ComputerUtilMana {
|
|||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
for (final SpellAbility am : getAIPlayableMana(c)) {
|
for (final SpellAbility am : getAIPlayableMana(c)) {
|
||||||
am.setActivatingPlayer(ai);
|
am.setActivatingPlayer(ai);
|
||||||
if (!checkPlayable || am.canPlay()) {
|
if (!checkPlayable || (am.canPlay() && am.checkRestrictions(ai))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,12 @@ package forge.ai;
|
|||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
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.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
|
import com.google.common.collect.*;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
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.StaticData;
|
||||||
import forge.card.CardStateName;
|
import forge.card.CardStateName;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
@@ -27,7 +16,6 @@ import forge.game.Game;
|
|||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
import forge.game.ability.AbilityFactory;
|
import forge.game.ability.AbilityFactory;
|
||||||
import forge.game.ability.AbilityKey;
|
|
||||||
import forge.game.ability.effects.DetachedCardEffect;
|
import forge.game.ability.effects.DetachedCardEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCloneStates;
|
import forge.game.card.CardCloneStates;
|
||||||
@@ -44,9 +32,7 @@ import forge.game.mana.ManaPool;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilityManaPart;
|
import forge.game.spellability.AbilityManaPart;
|
||||||
import forge.game.spellability.AbilitySub;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.trigger.TriggerType;
|
|
||||||
import forge.game.zone.PlayerZone;
|
import forge.game.zone.PlayerZone;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.item.IPaperCard;
|
import forge.item.IPaperCard;
|
||||||
@@ -770,23 +756,10 @@ public abstract class GameState {
|
|||||||
}
|
}
|
||||||
game.fireEvent(new GameEventAttackersDeclared(attackingPlayer, attackersMap));
|
game.fireEvent(new GameEventAttackersDeclared(attackingPlayer, attackersMap));
|
||||||
|
|
||||||
if (!combat.getAttackers().isEmpty()) {
|
|
||||||
List<GameEntity> attackedTarget = Lists.newArrayList();
|
|
||||||
for (final Card c : combat.getAttackers()) {
|
for (final Card c : combat.getAttackers()) {
|
||||||
attackedTarget.add(combat.getDefenderByAttacker(c));
|
CombatUtil.checkDeclaredAttacker(game, c, combat, false);
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
game.getTriggerHandler().resetActiveTriggers();
|
|
||||||
game.updateCombatForView();
|
game.updateCombatForView();
|
||||||
game.fireEvent(new GameEventCombatChanged());
|
game.fireEvent(new GameEventCombatChanged());
|
||||||
|
|
||||||
@@ -1346,7 +1319,7 @@ public abstract class GameState {
|
|||||||
}
|
}
|
||||||
else if (info.startsWith("OnAdventure")) {
|
else if (info.startsWith("OnAdventure")) {
|
||||||
String abAdventure = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell";
|
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();
|
StringBuilder sbPlay = new StringBuilder();
|
||||||
sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure");
|
sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure");
|
||||||
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");
|
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");
|
||||||
|
|||||||
@@ -530,7 +530,14 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CardCollectionView chooseCardsToDiscardUnlessType(int num, CardCollectionView hand, String uType, SpellAbility sa) {
|
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()) {
|
if (!cardsOfType.isEmpty()) {
|
||||||
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
|
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
|
||||||
return new CardCollection(toDiscard);
|
return new CardCollection(toDiscard);
|
||||||
@@ -1022,7 +1029,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
*/
|
*/
|
||||||
if (sa.isMayChooseNewTargets() && !sa.setupTargets()) {
|
if (sa.isMayChooseNewTargets() && !sa.setupTargets()) {
|
||||||
if (sa.isSpell()) {
|
if (sa.isSpell()) {
|
||||||
sa.getHostCard().ceaseToExist();
|
player.getGame().getAction().ceaseToExist(sa.getHostCard(), false);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1056,13 +1063,12 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
|
boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
|
||||||
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
||||||
Spell spell = (Spell) tgtSA;
|
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) {
|
if (noManaCost) {
|
||||||
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame());
|
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame());
|
||||||
} else {
|
}
|
||||||
return ComputerUtil.playStack(tgtSA, player, getGame());
|
return ComputerUtil.playStack(tgtSA, player, getGame());
|
||||||
}
|
}
|
||||||
} else
|
|
||||||
return false; // didn't play spell
|
return false; // didn't play spell
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -1086,7 +1092,6 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
boolean allCreatures = Iterables.all(Iterables.concat(pile1, pile2), CardPredicates.Presets.CREATURES);
|
boolean allCreatures = Iterables.all(Iterables.concat(pile1, pile2), CardPredicates.Presets.CREATURES);
|
||||||
int cmc1 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile1) : ComputerUtilCard.evaluatePermanentList(pile1);
|
int cmc1 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile1) : ComputerUtilCard.evaluatePermanentList(pile1);
|
||||||
int cmc2 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile2) : ComputerUtilCard.evaluatePermanentList(pile2);
|
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
|
// for now, this assumes that the outcome will be bad
|
||||||
// TODO: This should really have a ChooseLogic param to
|
// 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
|
// Chain of Acid
|
||||||
public static class ChainOfAcid {
|
public static class ChainOfAcid {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
@@ -159,8 +216,7 @@ public class SpecialCardAi {
|
|||||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
final Combat combat = ai.getGame().getCombat();
|
final Combat combat = ai.getGame().getCombat();
|
||||||
|
|
||||||
Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa);
|
Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa.getSubAbility());
|
||||||
animated.addType("Creature");
|
|
||||||
if (sa.getHostCard().canReceiveCounters(CounterEnumType.P1P1)) {
|
if (sa.getHostCard().canReceiveCounters(CounterEnumType.P1P1)) {
|
||||||
animated.addCounter(CounterEnumType.P1P1, 2, ai, false, null);
|
animated.addCounter(CounterEnumType.P1P1, 2, ai, false, null);
|
||||||
}
|
}
|
||||||
@@ -170,10 +226,6 @@ public class SpecialCardAi {
|
|||||||
|
|
||||||
return isOppEOT || isValuableAttacker || isValuableBlocker;
|
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
|
// 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
|
// Necropotence
|
||||||
public static class Necropotence {
|
public static class Necropotence {
|
||||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ public abstract class SpellAbilityAi {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Cost cost = sa.getPayCosts();
|
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())) {
|
if (!checkConditions(ai, sa, sa.getConditions())) {
|
||||||
SpellAbility sub = sa.getSubAbility();
|
SpellAbility sub = sa.getSubAbility();
|
||||||
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {
|
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.BidLife, BidLifeAi.class)
|
.put(ApiType.BidLife, BidLifeAi.class)
|
||||||
.put(ApiType.Bond, BondAi.class)
|
.put(ApiType.Bond, BondAi.class)
|
||||||
.put(ApiType.Branch, AlwaysPlayAi.class)
|
.put(ApiType.Branch, AlwaysPlayAi.class)
|
||||||
|
.put(ApiType.Camouflage, ChooseCardAi.class)
|
||||||
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
|
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
|
||||||
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
||||||
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
||||||
@@ -94,6 +95,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.Haunt, HauntAi.class)
|
.put(ApiType.Haunt, HauntAi.class)
|
||||||
.put(ApiType.ImmediateTrigger, ImmediateTriggerAi.class)
|
.put(ApiType.ImmediateTrigger, ImmediateTriggerAi.class)
|
||||||
.put(ApiType.Investigate, InvestigateAi.class)
|
.put(ApiType.Investigate, InvestigateAi.class)
|
||||||
|
.put(ApiType.Learn, LearnAi.class)
|
||||||
.put(ApiType.LoseLife, LifeLoseAi.class)
|
.put(ApiType.LoseLife, LifeLoseAi.class)
|
||||||
.put(ApiType.LosesGame, GameLossAi.class)
|
.put(ApiType.LosesGame, GameLossAi.class)
|
||||||
.put(ApiType.Mana, ManaEffectAi.class)
|
.put(ApiType.Mana, ManaEffectAi.class)
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = ai.getStrongestOpponent();
|
||||||
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
|
||||||
|
|
||||||
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
@@ -40,12 +39,13 @@ public class ActivateAbilityAi extends SpellAbilityAi {
|
|||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||||
return randomReturn;
|
return randomReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
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 TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import forge.game.cost.CostPutCounter;
|
|||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
|
import forge.game.player.PlayerActionConfirmMode;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.staticability.StaticAbilityContinuous;
|
import forge.game.staticability.StaticAbilityContinuous;
|
||||||
@@ -243,6 +244,11 @@ public class AnimateAi extends SpellAbilityAi {
|
|||||||
return true;
|
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) {
|
private boolean animateTgtAI(final SpellAbility sa) {
|
||||||
final Player ai = sa.getActivatingPlayer();
|
final Player ai = sa.getActivatingPlayer();
|
||||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
@@ -1129,8 +1130,7 @@ public class AttachAi extends SpellAbilityAi {
|
|||||||
for (Card target : list) {
|
for (Card target : list) {
|
||||||
for (Trigger t : target.getTriggers()) {
|
for (Trigger t : target.getTriggers()) {
|
||||||
if (t.getMode() == TriggerType.SpellCast) {
|
if (t.getMode() == TriggerType.SpellCast) {
|
||||||
final Map<String, String> params = t.getMapParams();
|
if ("Card.Self".equals(t.getParam("TargetsValid")) && "You".equals(t.getParam("ValidActivatingPlayer"))) {
|
||||||
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))) {
|
|
||||||
magnetList.add(target);
|
magnetList.add(target);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,18 +13,21 @@ public class BalanceAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
|
|
||||||
int diff = 0;
|
int diff = 0;
|
||||||
// TODO Add support for multiplayer logic
|
Player opp = aiPlayer.getWeakestOpponent();
|
||||||
final Player opp = aiPlayer.getWeakestOpponent();
|
|
||||||
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
|
|
||||||
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
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)) {
|
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() -
|
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
|
||||||
CardLists.filter(compPerms, 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());
|
CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size());
|
||||||
}
|
}
|
||||||
else if ("BalancePermanents".equals(logic)) {
|
else if ("BalancePermanents".equals(logic)) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -24,7 +25,7 @@ public class BidLifeAi extends SpellAbilityAi {
|
|||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (tgt.canTgtCreature()) {
|
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);
|
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import forge.game.card.Card;
|
|||||||
import forge.game.mana.ManaCostBeingPaid;
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.TargetChoices;
|
||||||
|
|
||||||
public class ChangeTargetsAi extends SpellAbilityAi {
|
public class ChangeTargetsAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -40,18 +41,20 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
|||||||
// nothing on stack, so nothing to target
|
// nothing on stack, so nothing to target
|
||||||
return false;
|
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
|
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
|
||||||
return true;
|
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
|
// if this does not target at all or already targets host, no need to redirect it again
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Card tgt : topSa.getTargets().getTargetCards()) {
|
for (Card tgt : topTargets.getTargetCards()) {
|
||||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) {
|
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),
|
// 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
|
// 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
|
// make sure not to redirect our own abilities
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -80,12 +83,22 @@ public class ChangeTargetsAi extends SpellAbilityAi {
|
|||||||
ManaCost normalizedMana = manaCost.getNormalizedMana();
|
ManaCost normalizedMana = manaCost.getNormalizedMana();
|
||||||
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
|
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
|
||||||
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
|
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
|
// 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;
|
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.resetTargets();
|
||||||
sa.getTargets().add(topSa);
|
sa.getTargets().add(topSa);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import com.google.common.collect.Iterables;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.AiBlockController;
|
import forge.ai.AiBlockController;
|
||||||
import forge.ai.AiCardMemory;
|
import forge.ai.AiCardMemory;
|
||||||
import forge.ai.AiController;
|
import forge.ai.AiController;
|
||||||
@@ -57,6 +58,7 @@ import forge.game.player.PlayerActionConfirmMode;
|
|||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
@@ -263,7 +265,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
||||||
ZoneType origin = null;
|
ZoneType origin = null;
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
|
||||||
|
|
||||||
if (sa.hasParam("Origin")) {
|
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
|
// if putting cards from hand to library and parent is drawing cards
|
||||||
// make sure this will actually do something:
|
// make sure this will actually do something:
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final Player opp = aiPlayer.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||||
if (tgt != null && tgt.canTgtPlayer()) {
|
if (tgt != null && tgt.canTgtPlayer()) {
|
||||||
boolean isCurse = sa.isCurse();
|
boolean isCurse = sa.isCurse();
|
||||||
if (isCurse && sa.canTarget(opp)) {
|
if (isCurse && sa.canTarget(opp)) {
|
||||||
@@ -530,7 +532,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
Iterable<Player> pDefined;
|
Iterable<Player> pDefined;
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if ((tgt != null) && tgt.canTgtPlayer()) {
|
if ((tgt != null) && tgt.canTgtPlayer()) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(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?
|
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
||||||
sa.setXManaCostPaid(xPay);
|
sa.setXManaCostPaid(xPay);
|
||||||
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
|
|
||||||
}
|
}
|
||||||
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
|
||||||
|
|
||||||
@@ -913,9 +914,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (sa.isSpell()) {
|
if (sa.isSpell()) {
|
||||||
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
|
||||||
}
|
}
|
||||||
//System.out.println("isPreferredTarget " + list);
|
|
||||||
if (sa.hasParam("AttachedTo")) {
|
if (sa.hasParam("AttachedTo")) {
|
||||||
//System.out.println("isPreferredTarget att " + list);
|
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
@@ -927,7 +926,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
//System.out.println("isPreferredTarget ok " + list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.size() < sa.getMinTargets()) {
|
if (list.size() < sa.getMinTargets()) {
|
||||||
@@ -1163,6 +1161,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
if (destination.equals(ZoneType.Battlefield) || origin.contains(ZoneType.Battlefield)) {
|
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);
|
final Card mostExpensive = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false);
|
||||||
if (mostExpensive.isCreature()) {
|
if (mostExpensive.isCreature()) {
|
||||||
// if a creature is most expensive take the best one
|
// if a creature is most expensive take the best one
|
||||||
@@ -1191,6 +1193,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return false;
|
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)) {
|
} else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) {
|
||||||
List<Card> nonLands = CardLists.getNotType(list, "Land");
|
List<Card> nonLands = CardLists.getNotType(list, "Land");
|
||||||
// Prefer to pull a creature, generally more useful for AI.
|
// Prefer to pull a creature, generally more useful for AI.
|
||||||
@@ -1482,9 +1489,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if ("DeathgorgeScavenger".equals(logic)) {
|
if ("DeathgorgeScavenger".equals(logic)) {
|
||||||
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
|
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
|
||||||
} else if ("ExtraplanarLens".equals(logic)) {
|
}
|
||||||
|
if ("ExtraplanarLens".equals(logic)) {
|
||||||
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
|
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
|
||||||
} else if ("ExileCombatThreat".equals(logic)) {
|
}
|
||||||
|
if ("ExileCombatThreat".equals(logic)) {
|
||||||
return doExileCombatThreatLogic(ai, sa);
|
return doExileCombatThreatLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1984,11 +1993,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toPay == 0) {
|
if (toPay == 0 || toPay <= usableManaSources) {
|
||||||
canBeSaved.add(potentialTgt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toPay <= usableManaSources) {
|
|
||||||
canBeSaved.add(potentialTgt);
|
canBeSaved.add(potentialTgt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.google.common.base.Predicates;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
@@ -74,10 +75,8 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
return !choices.isEmpty();
|
return !choices.isEmpty();
|
||||||
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
|
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
|
||||||
return choices.size() >= 2;
|
return choices.size() >= 2;
|
||||||
} else if (aiLogic.equals("Clone") || aiLogic.equals("Vesuva")) {
|
} else if (aiLogic.equals("Clone")) {
|
||||||
final String filter = aiLogic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
|
final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
|
||||||
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
|
||||||
|
|
||||||
choices = CardLists.getValidCards(choices, filter, host.getController(), host, sa);
|
choices = CardLists.getValidCards(choices, filter, host.getController(), host, sa);
|
||||||
return !choices.isEmpty();
|
return !choices.isEmpty();
|
||||||
} else if (aiLogic.equals("Never")) {
|
} 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();
|
return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host, sa).isEmpty();
|
||||||
} else if (aiLogic.equals("Duneblast")) {
|
} else if (aiLogic.equals("Duneblast")) {
|
||||||
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
CardCollection aiCreatures = ai.getCreaturesInPlay();
|
||||||
CardCollection oppCreatures = ai.getWeakestOpponent().getCreaturesInPlay();
|
CardCollection oppCreatures = AiAttackController.choosePreferredDefenderPlayer(ai).getCreaturesInPlay();
|
||||||
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
|
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
|
||||||
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
|
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
|
||||||
|
|
||||||
@@ -172,18 +171,13 @@ public class ChooseCardAi extends SpellAbilityAi {
|
|||||||
options = CardLists.filter(options, Presets.UNTAPPED);
|
options = CardLists.filter(options, Presets.UNTAPPED);
|
||||||
}
|
}
|
||||||
choice = ComputerUtilCard.getBestCreatureAI(options);
|
choice = ComputerUtilCard.getBestCreatureAI(options);
|
||||||
} else if (logic.equals("Clone") || logic.equals("Vesuva")) {
|
} else if (logic.equals("Clone")) {
|
||||||
final String filter = logic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary" :
|
final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
|
||||||
"Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
|
|
||||||
|
|
||||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||||
if (!newOptions.isEmpty()) {
|
if (!newOptions.isEmpty()) {
|
||||||
options = newOptions;
|
options = newOptions;
|
||||||
}
|
}
|
||||||
choice = ComputerUtilCard.getBestAI(options);
|
choice = ComputerUtilCard.getBestAI(options);
|
||||||
if (logic.equals("Vesuva") && "Vesuva".equals(choice.getName())) {
|
|
||||||
choice = null;
|
|
||||||
}
|
|
||||||
} else if ("RandomNonLand".equals(logic)) {
|
} else if ("RandomNonLand".equals(logic)) {
|
||||||
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host, sa);
|
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host, sa);
|
||||||
choice = Aggregates.random(options);
|
choice = Aggregates.random(options);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.google.common.collect.Iterables;
|
|||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpecialCardAi;
|
import forge.ai.SpecialCardAi;
|
||||||
@@ -34,9 +35,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String logic = sa.getParam("AILogic");
|
String logic = sa.getParam("AILogic");
|
||||||
if (logic.equals("MomirAvatar")) {
|
if (logic.equals("CursedScroll")) {
|
||||||
return SpecialCardAi.MomirVigAvatar.consider(ai, sa);
|
|
||||||
} else if (logic.equals("CursedScroll")) {
|
|
||||||
return SpecialCardAi.CursedScroll.consider(ai, sa);
|
return SpecialCardAi.CursedScroll.consider(ai, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +43,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
|||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (tgt.canOnlyTgtOpponent()) {
|
if (tgt.canOnlyTgtOpponent()) {
|
||||||
sa.getTargets().add(ai.getWeakestOpponent());
|
sa.getTargets().add(AiAttackController.choosePreferredDefenderPlayer(ai));
|
||||||
} else {
|
} else {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -16,7 +17,7 @@ public class ChooseEvenOddAi extends SpellAbilityAi {
|
|||||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
Player opp = aiPlayer.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -158,6 +158,22 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return others;
|
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)) {
|
} else if ("Fatespinner".equals(logic)) {
|
||||||
SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null;
|
SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null;
|
||||||
for (final SpellAbility sp : spells) {
|
for (final SpellAbility sp : spells) {
|
||||||
@@ -363,8 +379,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
|||||||
} else if ("Riot".equals(logic)) {
|
} else if ("Riot".equals(logic)) {
|
||||||
SpellAbility counterSA = spells.get(0), hasteSA = spells.get(1);
|
SpellAbility counterSA = spells.get(0), hasteSA = spells.get(1);
|
||||||
return preferHasteForRiot(sa, player) ? hasteSA : counterSA;
|
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
|
return spells.get(0); // return first choice if no logic found
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -16,7 +17,7 @@ public class ChooseNumberAi extends SpellAbilityAi {
|
|||||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
Player opp = aiPlayer.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.google.common.base.Predicates;
|
|||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
@@ -54,7 +55,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
|||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,13 +3,17 @@ package forge.ai.ability;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicates;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -95,11 +99,18 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||||
|
Card host = sa.getHostCard();
|
||||||
boolean chance = true;
|
boolean chance = true;
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
chance = cloneTgtAI(sa);
|
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:
|
// Improve AI for triggers. If source is a creature with:
|
||||||
@@ -171,18 +182,18 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||||
Player targetedPlayer, Map<String, Object> params) {
|
Player targetedPlayer, Map<String, Object> params) {
|
||||||
|
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
|
final String name = host.getName();
|
||||||
final Player ctrl = host.getController();
|
final Player ctrl = host.getController();
|
||||||
|
|
||||||
final Card cloneTarget = getCloneTarget(sa);
|
final Card cloneTarget = getCloneTarget(sa);
|
||||||
final boolean isOpp = cloneTarget.getController().isOpponentOf(sa.getActivatingPlayer());
|
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"));
|
final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary"));
|
||||||
|
|
||||||
String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.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
|
// TODO: rewrite this block so that this is done somehow more elegantly
|
||||||
if (canCloneLegendary) {
|
if (canCloneLegendary) {
|
||||||
@@ -201,12 +212,13 @@ public class CloneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options);
|
// prevent loop of choosing copy of same card
|
||||||
|
if (isVesuva) {
|
||||||
if (isVesuva && "Vesuva".equals(choice.getName())) {
|
options = CardLists.filter(options, Predicates.not(CardPredicates.sharesNameWith(host)));
|
||||||
choice = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options);
|
||||||
|
|
||||||
return choice;
|
return choice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package forge.ai.ability;
|
|||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
@@ -29,7 +30,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
CardCollection list =
|
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
|
// AI won't try to grab cards that are filtered out of AI decks on
|
||||||
// purpose
|
// purpose
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import forge.game.player.PlayerCollection;
|
|||||||
import forge.game.player.PlayerPredicates;
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
|
|
||||||
@@ -211,6 +212,10 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (t == null) {
|
while (t == null) {
|
||||||
|
// filter by MustTarget requirement
|
||||||
|
CardCollection originalList = new CardCollection(list);
|
||||||
|
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
|
||||||
|
|
||||||
if (planeswalkers > 0) {
|
if (planeswalkers > 0) {
|
||||||
t = ComputerUtilCard.getBestPlaneswalkerAI(list);
|
t = ComputerUtilCard.getBestPlaneswalkerAI(list);
|
||||||
} else if (creatures > 0) {
|
} else if (creatures > 0) {
|
||||||
@@ -238,6 +243,11 @@ public class ControlGainAi extends SpellAbilityAi {
|
|||||||
enchantments--;
|
enchantments--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore original list for next loop if filtered by MustTarget requirement
|
||||||
|
if (mustTargetFiltered) {
|
||||||
|
list = originalList;
|
||||||
|
}
|
||||||
|
|
||||||
if (!sa.canTarget(t)) {
|
if (!sa.canTarget(t)) {
|
||||||
list.remove(t);
|
list.remove(t);
|
||||||
t = null;
|
t = null;
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
|||||||
return false;
|
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);
|
return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
|
||||||
} else if ("AtEOT".equals(aiLogic)) {
|
} else if ("AtEOT".equals(aiLogic)) {
|
||||||
return ph.is(PhaseType.END_OF_TURN);
|
return ph.is(PhaseType.END_OF_TURN);
|
||||||
|
|||||||
@@ -372,6 +372,10 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
|
||||||
if (!aiList.isEmpty()) {
|
if (!aiList.isEmpty()) {
|
||||||
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
|
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
|
||||||
@@ -392,7 +396,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
if (cType.is(CounterEnumType.P1P1) && card.hasKeyword(Keyword.UNDYING)) {
|
if (cType.is(CounterEnumType.P1P1) && card.hasKeyword(Keyword.UNDYING)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (cType.is(CounterEnumType.M1M1) && card.hasKeyword(Keyword.PERSIST)) {
|
if (cType.is(CounterEnumType.M1M1)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,11 +419,10 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// move counter to opponents creature but only if you can not steal
|
// move counter to opponents creature but only if you can not steal them
|
||||||
// them
|
// try to move to something useless or something that would leave play
|
||||||
// try to move to something useless or something that would leave
|
|
||||||
// play
|
|
||||||
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
|
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
|
||||||
if (!oppList.isEmpty()) {
|
if (!oppList.isEmpty()) {
|
||||||
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
|
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
|
||||||
@@ -441,7 +444,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (best.isEmpty()) {
|
if (best.isEmpty()) {
|
||||||
best = aiList;
|
best = oppList;
|
||||||
}
|
}
|
||||||
|
|
||||||
Card card = ComputerUtilCard.getBestCreatureAI(best);
|
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
|
// or for source -> multiple defined
|
||||||
@Override
|
@Override
|
||||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
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.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiProps;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
|
import forge.ai.PlayerControllerAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -121,6 +123,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
final CounterType poison = CounterType.get(CounterEnumType.POISON);
|
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
|
// because countertype can't be chosen anymore, only look for posion counters
|
||||||
for (final Player p : Iterables.filter(options, Player.class)) {
|
for (final Player p : Iterables.filter(options, Player.class)) {
|
||||||
if (p.isOpponentOf(ai)) {
|
if (p.isOpponentOf(ai)) {
|
||||||
@@ -128,7 +131,8 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
|||||||
return (T)p;
|
return (T)p;
|
||||||
}
|
}
|
||||||
} else {
|
} 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;
|
return (T)p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ import forge.game.player.PlayerCollection;
|
|||||||
import forge.game.player.PlayerPredicates;
|
import forge.game.player.PlayerPredicates;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.trigger.Trigger;
|
||||||
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
@@ -313,7 +315,12 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
} else if (logic.startsWith("MoveCounter")) {
|
} else if (logic.startsWith("MoveCounter")) {
|
||||||
return doMoveCounterLogic(ai, sa, ph);
|
return doMoveCounterLogic(ai, sa, ph);
|
||||||
} else if (logic.equals("CrawlingBarrens")) {
|
} 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) {
|
if (!sa.metConditions() && sa.getSubAbility() == null) {
|
||||||
@@ -401,19 +408,37 @@ public class CountersPutAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("Polukranos".equals(logic)) {
|
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()){
|
if (!targets.isEmpty()){
|
||||||
boolean canSurvive = false;
|
boolean canSurvive = false;
|
||||||
for (Card humanCreature : targets) {
|
for (Card humanCreature : targets) {
|
||||||
if (!FightAi.canKill(humanCreature, source, 0)){
|
if (!FightAi.canKill(humanCreature, source, 0)){
|
||||||
canSurvive = true;
|
canSurvive = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!canSurvive){
|
if (!canSurvive){
|
||||||
return false;
|
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.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerDamageDone;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
|||||||
boolean dmgByCardsInHand = false;
|
boolean dmgByCardsInHand = false;
|
||||||
|
|
||||||
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&
|
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;
|
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
|
// 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!
|
// If has triggered ability on dealing damage to an opponent, go for it!
|
||||||
Card hostcard = sa.getHostCard();
|
Card hostcard = sa.getHostCard();
|
||||||
for (Trigger trig : hostcard.getTriggers()) {
|
for (Trigger trig : hostcard.getTriggers()) {
|
||||||
if (trig instanceof TriggerDamageDone) {
|
if (trig.getMode() == TriggerType.DamageDone) {
|
||||||
if (("Opponent".equals(trig.getParam("ValidTarget")))
|
if (("Opponent".equals(trig.getParam("ValidTarget")))
|
||||||
&& (!"True".equals(trig.getParam("CombatDamage")))) {
|
&& (!"True".equals(trig.getParam("CombatDamage")))) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import forge.game.spellability.AbilitySub;
|
|||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetChoices;
|
import forge.game.spellability.TargetChoices;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
@@ -343,6 +344,9 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
final Game game = source.getGame();
|
final Game game = source.getGame();
|
||||||
List<Card> hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game);
|
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>() {
|
CardCollection killables = CardLists.filter(hPlay, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
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.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
@@ -26,9 +27,6 @@ import forge.game.spellability.TargetRestrictions;
|
|||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class DebuffAi extends SpellAbilityAi {
|
public class DebuffAi extends SpellAbilityAi {
|
||||||
// *************************************************************************
|
|
||||||
// ***************************** Debuff ************************************
|
|
||||||
// *************************************************************************
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
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)) {
|
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
||||||
Card t = null;
|
Card t = null;
|
||||||
// boolean goodt = false;
|
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) {
|
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.
|
* @return a CardCollection.
|
||||||
*/
|
*/
|
||||||
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
|
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);
|
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
|
||||||
if (!list.isEmpty()) {
|
if (!list.isEmpty()) {
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
return c.hasAnyKeyword(kws); // don't add duplicate negative
|
return c.hasAnyKeyword(kws); // don't add duplicate negative keywords
|
||||||
// keywords
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
} // getCurseCreatures()
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -216,7 +212,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
list.remove(c);
|
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 CardCollection forced = CardLists.filterControlledBy(list, ai);
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
@@ -242,8 +238,7 @@ public class DebuffAi extends SpellAbilityAi {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - if forced targeting, just pick something without the given
|
// TODO - if forced targeting, just pick something without the given keyword
|
||||||
// keyword
|
|
||||||
Card c;
|
Card c;
|
||||||
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
if (CardLists.getNotType(forced, "Creature").size() == 0) {
|
||||||
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
c = ComputerUtilCard.getWorstCreatureAI(forced);
|
||||||
|
|||||||
@@ -2,15 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
|
||||||
import forge.ai.AiController;
|
import forge.ai.*;
|
||||||
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.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -26,6 +18,7 @@ import forge.game.phase.PhaseHandler;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class DestroyAi extends SpellAbilityAi {
|
public class DestroyAi extends SpellAbilityAi {
|
||||||
@@ -122,17 +115,21 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
CardCollection list;
|
CardCollection list;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Targeting
|
// Targeting
|
||||||
if (sa.usesTargeting()) {
|
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
|
// 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,11 +137,11 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
int maxTargets;
|
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.
|
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
|
||||||
maxTargets = ComputerUtilCost.getMaxXValue(sa, ai);
|
maxTargets = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
// need to set XPaid to get the right number for
|
// need to set XPaid to get the right number for
|
||||||
sa.setXManaCostPaid(maxTargets);
|
sa.getRootAbility().setXManaCostPaid(maxTargets);
|
||||||
// need to check for maxTargets
|
// need to check for maxTargets
|
||||||
maxTargets = Math.min(maxTargets, sa.getMaxTargets());
|
maxTargets = Math.min(maxTargets, sa.getMaxTargets());
|
||||||
} else {
|
} else {
|
||||||
@@ -226,6 +223,10 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
// target loop
|
// target loop
|
||||||
// TODO use can add more Targets
|
// TODO use can add more Targets
|
||||||
while (sa.getTargets().size() < maxTargets) {
|
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 (list.isEmpty()) {
|
||||||
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
sa.resetTargets();
|
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);
|
list.remove(choice);
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,12 +70,12 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
return doMassRemovalLogic(ai, sa);
|
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 Card source = sa.getHostCard();
|
||||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
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")) {
|
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
|
return true; // e.g. Tetzimoc, Primal Death, where we want to cast the permanent even if the removal trigger does nothing
|
||||||
@@ -93,10 +93,10 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
valid = valid.replace("X", Integer.toString(xPay));
|
valid = valid.replace("X", Integer.toString(xPay));
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield),
|
// TODO should probably sort results when targeted to use on biggest threat instead of first match
|
||||||
valid.split(","), source.getController(), source, sa);
|
for (Player opponent: ai.getOpponents()) {
|
||||||
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||||
source.getController(), source, sa);
|
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||||
|
|
||||||
opplist = CardLists.filter(opplist, predicate);
|
opplist = CardLists.filter(opplist, predicate);
|
||||||
ailist = CardLists.filter(ailist, predicate);
|
ailist = CardLists.filter(ailist, predicate);
|
||||||
@@ -135,8 +135,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if only creatures are affected evaluate both lists and pass only if
|
// if only creatures are affected evaluate both lists and pass only if human creatures are more valuable
|
||||||
// human creatures are more valuable
|
|
||||||
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
|
||||||
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -167,7 +166,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
} // only lands involved
|
} // only lands involved
|
||||||
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
|
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()) {
|
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
|
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
|
||||||
@@ -188,4 +187,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ import java.util.Map;
|
|||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
@@ -32,7 +33,7 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
|
|
||||||
@@ -120,7 +121,7 @@ public class DigAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final SpellAbility root = sa.getRootAbility();
|
final SpellAbility root = sa.getRootAbility();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (mandatory && sa.canTarget(opp)) {
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
@@ -19,7 +20,7 @@ public class DigMultipleAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
final Card host = sa.getHostCard();
|
final Card host = sa.getHostCard();
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ public class DigMultipleAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (mandatory && sa.canTarget(opp)) {
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -31,10 +32,8 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
chance = 1;
|
chance = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
|
||||||
|
|
||||||
Player libraryOwner = ai;
|
Player libraryOwner = ai;
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
|
|
||||||
if ("DontMillSelf".equals(logic)) {
|
if ("DontMillSelf".equals(logic)) {
|
||||||
// A card that digs for specific things and puts everything revealed before it into graveyard
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
|
||||||
return randomReturn;
|
return randomReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.isCurse()) {
|
if (sa.isCurse()) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
@@ -60,11 +61,9 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
if (players.get(0) == ai) {
|
if (players.get(0) == ai) {
|
||||||
// the ai should only be using something like this if he has
|
// the ai should only be using something like this if he has
|
||||||
// few cards in hand,
|
// few cards in hand,
|
||||||
// cards like this better have a good drawback to be in the
|
// cards like this better have a good drawback to be in the AIs deck
|
||||||
// AIs deck
|
|
||||||
} else {
|
} else {
|
||||||
// defined to the human, so that's fine as long the human
|
// defined to the human, so that's fine as long the human has cards
|
||||||
// has cards
|
|
||||||
if (!humanHasHand) {
|
if (!humanHasHand) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -170,7 +169,7 @@ public class DiscardAi extends SpellAbilityAi {
|
|||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (!discardTargetAI(ai, sa)) {
|
if (!discardTargetAI(ai, sa)) {
|
||||||
if (mandatory && sa.canTarget(opp)) {
|
if (mandatory && sa.canTarget(opp)) {
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ public class DrainManaAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (tgt == null) {
|
if (tgt == null) {
|
||||||
// assume we are looking to tap human's stuff
|
// assume we are looking to tap human's stuff
|
||||||
// TODO - check for things with untap abilities, and don't tap
|
// TODO - check for things with untap abilities, and don't tap those.
|
||||||
// those.
|
|
||||||
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||||
|
|
||||||
if (!defined.contains(opp)) {
|
if (!defined.contains(opp)) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import forge.game.keyword.Keyword;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilitySub;
|
import forge.game.spellability.AbilitySub;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.staticability.StaticAbilityMustTarget;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
@@ -50,6 +51,9 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
|
||||||
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
|
||||||
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
|
||||||
|
// Filter MustTarget requirements
|
||||||
|
StaticAbilityMustTarget.filterMustTargetCards(ai, humCreatures, sa);
|
||||||
|
|
||||||
if (humCreatures.isEmpty())
|
if (humCreatures.isEmpty())
|
||||||
return false; //prevent IndexOutOfBoundsException on MOJHOSTO variant
|
return false; //prevent IndexOutOfBoundsException on MOJHOSTO variant
|
||||||
|
|
||||||
|
|||||||
@@ -16,14 +16,32 @@ public class FlipACoinAi extends SpellAbilityAi {
|
|||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
|
|
||||||
if (sa.hasParam("AILogic")) {
|
if (sa.hasParam("AILogic")) {
|
||||||
String AILogic = sa.getParam("AILogic");
|
String ailogic = sa.getParam("AILogic");
|
||||||
if (AILogic.equals("Never")) {
|
if (ailogic.equals("Never")) {
|
||||||
return false;
|
return false;
|
||||||
} else if (AILogic.equals("PhaseOut")) {
|
} else if (ailogic.equals("PhaseOut")) {
|
||||||
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
|
||||||
return false;
|
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) ) {
|
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,11 +29,10 @@ public class GameLossAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
// Phage the Untouchable
|
// Phage the Untouchable
|
||||||
// (Final Fortune would need to attach it's delayed trigger to a
|
// (Final Fortune would need to attach it's delayed trigger to a
|
||||||
// specific turn, which can't be done yet)
|
// specific turn, which can't be done yet)
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = ai.getGame().getCombat().getDefenderPlayerByAttacker(sa.getHostCard());
|
||||||
|
|
||||||
if (!mandatory && opp.cantLose()) {
|
if (!mandatory && opp.cantLose()) {
|
||||||
return false;
|
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;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -19,7 +20,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||||
final int myLife = aiPlayer.getLife();
|
final int myLife = aiPlayer.getLife();
|
||||||
Player opponent = aiPlayer.getWeakestOpponent();
|
Player opponent = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
|
||||||
final int hLife = opponent.getLife();
|
final int hLife = opponent.getLife();
|
||||||
|
|
||||||
if (!aiPlayer.canGainLife()) {
|
if (!aiPlayer.canGainLife()) {
|
||||||
@@ -75,7 +76,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
|
|||||||
final boolean mandatory) {
|
final boolean mandatory) {
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.AiProps;
|
import forge.ai.AiProps;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
@@ -154,7 +155,7 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
|
|||||||
final boolean mandatory) {
|
final boolean mandatory) {
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final int myLife = ai.getLife();
|
final int myLife = ai.getLife();
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = ai.getStrongestOpponent();
|
||||||
final int hlife = opponent.getLife();
|
final int hlife = opponent.getLife();
|
||||||
final String amountStr = sa.getParam("LifeAmount");
|
final String amountStr = sa.getParam("LifeAmount");
|
||||||
|
|
||||||
@@ -36,8 +36,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle proper calculation of X values based on Cost and what
|
// TODO handle proper calculation of X values based on Cost and what would be paid
|
||||||
// would be paid
|
|
||||||
int amount;
|
int amount;
|
||||||
// we shouldn't have to worry too much about PayX for SetLife
|
// we shouldn't have to worry too much about PayX for SetLife
|
||||||
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
|
||||||
@@ -58,11 +57,9 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
if (tgt.canOnlyTgtOpponent()) {
|
if (tgt.canOnlyTgtOpponent()) {
|
||||||
sa.getTargets().add(opponent);
|
sa.getTargets().add(opponent);
|
||||||
// if we can only target the human, and the Human's life
|
// if we can only target the human, and the Human's life
|
||||||
// would
|
// would go up, don't play it.
|
||||||
// go up, don't play it.
|
|
||||||
// possibly add a combo here for Magister Sphinx and
|
// possibly add a combo here for Magister Sphinx and
|
||||||
// Higedetsu's
|
// Higedetsu's (sp?) Second Rite
|
||||||
// (sp?) Second Rite
|
|
||||||
if ((amount > hlife) || !opponent.canLoseLife()) {
|
if ((amount > hlife) || !opponent.canLoseLife()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -81,8 +78,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
if (sa.getParam("Defined").equals("Player")) {
|
if (sa.getParam("Defined").equals("Player")) {
|
||||||
if (amount == 0) {
|
if (amount == 0) {
|
||||||
return false;
|
return false;
|
||||||
} else if (myLife > amount) { // will decrease computer's
|
} else if (myLife > amount) { // will decrease computer's life
|
||||||
// life
|
|
||||||
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
|
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -104,7 +100,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final int myLife = ai.getLife();
|
final int myLife = ai.getLife();
|
||||||
final Player opponent = ai.getWeakestOpponent();
|
final Player opponent = ai.getStrongestOpponent();
|
||||||
final int hlife = opponent.getLife();
|
final int hlife = opponent.getLife();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
|
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 gaining life, target self.
|
||||||
// if the Target is modifying how much life is gained, this needs to
|
// if the Target is modifying how much life is gained, this needs to be handled better
|
||||||
// be handled better
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|||||||
@@ -30,13 +30,12 @@ public class PowerExchangeAi extends SpellAbilityAi {
|
|||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
List<Card> list =
|
List<Card> list =
|
||||||
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
|
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
|
// AI won't try to grab cards that are filtered out of AI decks on purpose
|
||||||
// purpose
|
|
||||||
list = CardLists.filter(list, new Predicate<Card>() {
|
list = CardLists.filter(list, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final Card c) {
|
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);
|
CardLists.sortByPowerAsc(list);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.google.common.base.Predicate;
|
|||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCombat;
|
import forge.ai.ComputerUtilCombat;
|
||||||
@@ -201,7 +202,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
|||||||
final Game game = ai.getGame();
|
final Game game = ai.getGame();
|
||||||
final Combat combat = game.getCombat();
|
final Combat combat = game.getCombat();
|
||||||
final PhaseHandler ph = game.getPhaseHandler();
|
final PhaseHandler ph = game.getPhaseHandler();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
final int newPower = card.getNetCombatDamage() + attack;
|
final int newPower = card.getNetCombatDamage() + attack;
|
||||||
//int defense = getNumDefense(sa);
|
//int defense = getNumDefense(sa);
|
||||||
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
|
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)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -64,15 +58,9 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
|
final Player opp = ai.getStrongestOpponent();
|
||||||
|
|
||||||
if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) {
|
if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
@@ -85,6 +73,18 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
return true;
|
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()) {
|
if (!game.getStack().isEmpty() && !sa.isCurse()) {
|
||||||
return pumpAgainstRemoval(ai, sa, comp);
|
return pumpAgainstRemoval(ai, sa, comp);
|
||||||
}
|
}
|
||||||
@@ -139,8 +139,7 @@ public class PumpAllAi extends PumpAiBase {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluate both lists and pass only if human creatures are more
|
// evaluate both lists and pass only if human creatures are more valuable
|
||||||
// valuable
|
|
||||||
return (ComputerUtilCard.evaluateCreatureList(comp) + 200) < ComputerUtilCard.evaluateCreatureList(human);
|
return (ComputerUtilCard.evaluateCreatureList(comp) + 200) < ComputerUtilCard.evaluateCreatureList(human);
|
||||||
} // end Curse
|
} // end Curse
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.AiController;
|
import forge.ai.AiController;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.PlayerControllerAi;
|
import forge.ai.PlayerControllerAi;
|
||||||
@@ -14,7 +15,7 @@ public class RepeatAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if (!opp.canBeTargetedBy(sa)) {
|
if (!opp.canBeTargetedBy(sa)) {
|
||||||
@@ -44,9 +45,8 @@ public class RepeatAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
if (sa.canTarget(opp)) {
|
if (sa.canTarget(opp)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import forge.game.card.CardPredicates.Presets;
|
|||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilitySub;
|
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
@@ -67,7 +66,7 @@ public class RepeatEachAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
} else if ("AllPlayerLoseLife".equals(logic)) {
|
} else if ("AllPlayerLoseLife".equals(logic)) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
AbilitySub repeat = sa.getAdditionalAbility("RepeatSubAbility");
|
SpellAbility repeat = sa.getAdditionalAbility("RepeatSubAbility");
|
||||||
|
|
||||||
String svar = repeat.getSVar(repeat.getParam("LifeAmount"));
|
String svar = repeat.getSVar(repeat.getParam("LifeAmount"));
|
||||||
// replace RememberedPlayerCtrl with YouCtrl
|
// replace RememberedPlayerCtrl with YouCtrl
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ import forge.game.spellability.TargetRestrictions;
|
|||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class SacrificeAi extends SpellAbilityAi {
|
public class SacrificeAi extends SpellAbilityAi {
|
||||||
// **************************************************************
|
|
||||||
// *************************** Sacrifice ***********************
|
|
||||||
// **************************************************************
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
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:
|
// Improve AI for triggers. If source is a creature with:
|
||||||
// When ETB, sacrifice a creature. Check to see if the AI has something
|
// When ETB, sacrifice a creature. Check to see if the AI has something to sacrifice
|
||||||
// to sacrifice
|
|
||||||
|
|
||||||
// Eventually, we can call the trigger of ETB abilities with not
|
// Eventually, we can call the trigger of ETB abilities with not
|
||||||
// mandatory as part of the checks to cast something
|
// 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) {
|
private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa) {
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
final boolean destroy = sa.hasParam("Destroy");
|
final boolean destroy = sa.hasParam("Destroy");
|
||||||
|
|
||||||
Player opp = ai.getWeakestOpponent();
|
Player opp = ai.getStrongestOpponent();
|
||||||
|
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
@@ -109,8 +104,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount));
|
sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
final int half = (amount / 2) + (amount % 2); // Half of amount
|
final int half = (amount / 2) + (amount % 2); // Half of amount rounded up
|
||||||
// rounded up
|
|
||||||
|
|
||||||
// If the Human has at least half rounded up of the amount to be
|
// If the Human has at least half rounded up of the amount to be
|
||||||
// sacrificed, cast the spell
|
// sacrificed, cast the spell
|
||||||
@@ -130,8 +124,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
// If Sacrifice hits both players:
|
// If Sacrifice hits both players:
|
||||||
// Only cast it if Human has the full amount of valid
|
// Only cast it if Human has the full amount of valid
|
||||||
// Only cast it if AI doesn't have 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
|
// TODO: Cast if the type is favorable: my "worst" valid is worse than his "worst" valid
|
||||||
// worse than his "worst" valid
|
|
||||||
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
|
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
|
||||||
int amount = AbilityUtils.calculateAmount(source, num, sa);
|
int amount = AbilityUtils.calculateAmount(source, num, sa);
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,12 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.ComputerUtilMana;
|
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
|
||||||
import forge.game.card.CardLists;
|
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
import forge.util.TextUtil;
|
|
||||||
|
|
||||||
public class SacrificeAllAi extends SpellAbilityAi {
|
public class SacrificeAllAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -22,22 +17,7 @@ public class SacrificeAllAi extends SpellAbilityAi {
|
|||||||
// based on what the expected targets could be
|
// based on what the expected targets could be
|
||||||
final Cost abCost = sa.getPayCosts();
|
final Cost abCost = sa.getPayCosts();
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
String valid = "";
|
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (abCost != null) {
|
if (abCost != null) {
|
||||||
// AI currently disabled for some costs
|
// AI currently disabled for some costs
|
||||||
@@ -46,29 +26,19 @@ public class SacrificeAllAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// prevent run-away activations - first time will always return true
|
||||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
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);
|
return ((MyRandom.getRandom().nextFloat() < .9667) && chance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,12 @@ package forge.ai.ability;
|
|||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilMana;
|
import forge.ai.ComputerUtilMana;
|
||||||
|
import forge.ai.SpecialCardAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.Card;
|
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.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
import forge.game.card.CardPredicates;
|
||||||
import forge.game.card.CounterEnumType;
|
|
||||||
import forge.game.phase.PhaseHandler;
|
import forge.game.phase.PhaseHandler;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -47,6 +45,11 @@ public class ScryAi extends SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
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
|
// 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
|
// 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,
|
// 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)) {
|
if ("Never".equals(aiLogic)) {
|
||||||
return false;
|
return false;
|
||||||
} else if ("BrainJar".equals(aiLogic)) {
|
} else if ("BrainJar".equals(aiLogic)) {
|
||||||
final Card source = sa.getHostCard();
|
return SpecialCardAi.BrainInAJar.consider(ai, sa);
|
||||||
|
} else if ("MultipleChoice".equals(aiLogic)) {
|
||||||
int counterNum = source.getCounters(CounterEnumType.CHARGE);
|
return SpecialCardAi.MultipleChoice.consider(ai, sa);
|
||||||
// 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 true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ public class TapAi extends TapAiBase {
|
|||||||
// Set PayX here to maximum value.
|
// Set PayX here to maximum value.
|
||||||
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
|
||||||
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
|
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
|
||||||
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.util.List;
|
|||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
@@ -109,7 +110,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
|||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) {
|
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();
|
final Game game = ai.getGame();
|
||||||
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||||
tapList = CardLists.getTargetableCards(tapList, sa);
|
tapList = CardLists.getTargetableCards(tapList, sa);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
@@ -28,7 +29,7 @@ public class TwoPilesAi extends SpellAbilityAi {
|
|||||||
valid = sa.getParam("ValidCards");
|
valid = sa.getParam("ValidCards");
|
||||||
}
|
}
|
||||||
|
|
||||||
final Player opp = ai.getWeakestOpponent();
|
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||||
|
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
if (tgt != null) {
|
if (tgt != null) {
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ public class UnattachAllAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final Card card = sa.getHostCard();
|
final Card card = sa.getHostCard();
|
||||||
final Player opp = ai.getWeakestOpponent();
|
|
||||||
// Check if there are any valid targets
|
// Check if there are any valid targets
|
||||||
List<GameObject> targets = new ArrayList<>();
|
List<GameObject> targets = new ArrayList<>();
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||||
@@ -66,8 +65,8 @@ public class UnattachAllAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
|
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
|
||||||
Card newTarget = (Card) targets.get(0);
|
Card newTarget = (Card) targets.get(0);
|
||||||
//don't equip human creatures
|
//don't equip opponent creatures
|
||||||
if (newTarget.getController().equals(opp)) {
|
if (!newTarget.getController().equals(ai)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.util.Map;
|
|||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
|
|
||||||
|
import forge.ai.AiAttackController;
|
||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.ComputerUtilAbility;
|
import forge.ai.ComputerUtilAbility;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
@@ -130,7 +131,8 @@ public class UntapAi extends SpellAbilityAi {
|
|||||||
Player targetController = ai;
|
Player targetController = ai;
|
||||||
|
|
||||||
if (sa.isCurse()) {
|
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);
|
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);
|
CardCollection untapList = targetUntapped ? list : CardLists.filter(list, Presets.TAPPED);
|
||||||
// filter out enchantments and planeswalkers, their tapped state doesn't
|
// filter out enchantments and planeswalkers, their tapped state doesn't matter.
|
||||||
// matter.
|
|
||||||
final String[] tappablePermanents = {"Creature", "Land", "Artifact"};
|
final String[] tappablePermanents = {"Creature", "Land", "Artifact"};
|
||||||
untapList = CardLists.getValidCards(untapList, tappablePermanents, source.getController(), source, sa);
|
untapList = CardLists.getValidCards(untapList, tappablePermanents, source.getController(), source, sa);
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import forge.ai.ComputerUtilAbility;
|
|||||||
import forge.ai.ComputerUtilCost;
|
import forge.ai.ComputerUtilCost;
|
||||||
import forge.ai.ability.ChangeZoneAi;
|
import forge.ai.ability.ChangeZoneAi;
|
||||||
import forge.ai.ability.ExploreAi;
|
import forge.ai.ability.ExploreAi;
|
||||||
|
import forge.ai.ability.LearnAi;
|
||||||
import forge.ai.simulation.GameStateEvaluator.Score;
|
import forge.ai.simulation.GameStateEvaluator.Score;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
@@ -423,6 +424,8 @@ public class SpellAbilityPicker {
|
|||||||
}
|
}
|
||||||
if (sa.getApi() == ApiType.Explore) {
|
if (sa.getApi() == ApiType.Explore) {
|
||||||
return ExploreAi.shouldPutInGraveyard(fetchList, decider);
|
return ExploreAi.shouldPutInGraveyard(fetchList, decider);
|
||||||
|
} else if (sa.getApi() == ApiType.Learn) {
|
||||||
|
return LearnAi.chooseCardToLearn(fetchList, decider, sa);
|
||||||
} else {
|
} else {
|
||||||
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>1.6.40-SNAPSHOT</version>
|
<version>1.6.40</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-core</artifactId>
|
<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
|
// if there's a 1st art variant try without it for .fullborder images
|
||||||
file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder"));
|
file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder"));
|
||||||
if (file != null) { return file; }
|
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
|
// if there's an art variant try without it for .full images
|
||||||
file = findFile(dir, filename.replaceAll("[0-9].full",".full"));
|
file = findFile(dir, filename.replaceAll("[0-9].full",".full"));
|
||||||
if (file != null) { return file; }
|
if (file != null) { return file; }
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package forge;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -79,16 +78,30 @@ public class StaticData {
|
|||||||
this.customEditions = new CardEdition.Collection(new CardEdition.Reader(new File(customEditionsFolder)));
|
this.customEditions = new CardEdition.Collection(new CardEdition.Reader(new File(customEditionsFolder)));
|
||||||
this.prefferedArt = prefferedArt;
|
this.prefferedArt = prefferedArt;
|
||||||
lastInstance = this;
|
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> regularCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||||
final Map<String, CardRules> variantsCards = 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);
|
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()) {
|
for (CardRules card : cardReader.loadCards()) {
|
||||||
if (null == card) continue;
|
if (null == card) continue;
|
||||||
|
|
||||||
final String cardName = card.getName();
|
final String cardName = card.getName();
|
||||||
|
|
||||||
|
if (!loadNonLegalCards && !card.getType().isBasicLand() && funnyCards.contains(cardName))
|
||||||
|
filtered.add(cardName);
|
||||||
|
|
||||||
if (card.isVariant()) {
|
if (card.isVariant()) {
|
||||||
variantsCards.put(cardName, card);
|
variantsCards.put(cardName, card);
|
||||||
} else {
|
} else {
|
||||||
@@ -104,15 +117,18 @@ public class StaticData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!filtered.isEmpty()) {
|
||||||
|
Collections.sort(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
commonCards = new CardDb(regularCards, editions);
|
commonCards = new CardDb(regularCards, editions, filtered);
|
||||||
variantCards = new CardDb(variantsCards, editions);
|
variantCards = new CardDb(variantsCards, editions, filtered);
|
||||||
customCards = new CardDb(customizedCards, customEditions);
|
customCards = new CardDb(customizedCards, customEditions, filtered);
|
||||||
|
|
||||||
//must initialize after establish field values for the sake of card image logic
|
//must initialize after establish field values for the sake of card image logic
|
||||||
commonCards.initialize(false, false, enableUnknownCards, loadNonLegalCards);
|
commonCards.initialize(false, false, enableUnknownCards);
|
||||||
variantCards.initialize(false, false, enableUnknownCards, loadNonLegalCards);
|
variantCards.initialize(false, false, enableUnknownCards);
|
||||||
customCards.initialize(false, false, enableUnknownCards, loadNonLegalCards);
|
customCards.initialize(false, false, enableUnknownCards);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -125,14 +141,6 @@ public class StaticData {
|
|||||||
}
|
}
|
||||||
allTokens = new TokenDb(tokens, editions);
|
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() {
|
public static StaticData instance() {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import java.util.Set;
|
|||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
@@ -55,6 +56,8 @@ import forge.util.TextUtil;
|
|||||||
public final class CardDb implements ICardDatabase, IDeckGenPool {
|
public final class CardDb implements ICardDatabase, IDeckGenPool {
|
||||||
public final static String foilSuffix = "+";
|
public final static String foilSuffix = "+";
|
||||||
public final static char NameSetSeparator = '|';
|
public final static char NameSetSeparator = '|';
|
||||||
|
private final String exlcudedCardName = "Concentrate";
|
||||||
|
private final String exlcudedCardSet = "DS0";
|
||||||
|
|
||||||
// need this to obtain cardReference by name+set+artindex
|
// 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());
|
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, String> alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
|
||||||
private final Map<String, Integer> artIds = new HashMap<>();
|
private final Map<String, Integer> artIds = new HashMap<>();
|
||||||
|
|
||||||
private final Collection<PaperCard> roAllCards = Collections.unmodifiableCollection(allCardsByName.values());
|
|
||||||
private final CardEdition.Collection editions;
|
private final CardEdition.Collection editions;
|
||||||
|
private List<String> filtered;
|
||||||
|
|
||||||
public enum SetPreference {
|
public enum SetPreference {
|
||||||
Latest(false),
|
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.rulesByName = rules;
|
||||||
this.editions = editions0;
|
this.editions = editions0;
|
||||||
|
|
||||||
// create faces list from rules
|
// create faces list from rules
|
||||||
for (final CardRules rule : rules.values() ) {
|
for (final CardRules rule : rules.values() ) {
|
||||||
|
if (filteredCards.contains(rule.getName()) && !exlcudedCardName.equalsIgnoreCase(rule.getName()))
|
||||||
|
continue;
|
||||||
final ICardFace main = rule.getMainPart();
|
final ICardFace main = rule.getMainPart();
|
||||||
facesByName.put(main.getName(), main);
|
facesByName.put(main.getName(), main);
|
||||||
if (main.getAltName() != null) {
|
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) {
|
private void addSetCard(CardEdition e, CardInSet cis, CardRules cr) {
|
||||||
int artIdx = 1;
|
int artIdx = 1;
|
||||||
String key = e.getCode() + "/" + cis.name;
|
String key = e.getCode() + "/" + cis.name;
|
||||||
@@ -182,33 +192,24 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
reIndex();
|
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<>();
|
Set<String> allMissingCards = new LinkedHashSet<>();
|
||||||
List<String> missingCards = new ArrayList<>();
|
List<String> missingCards = new ArrayList<>();
|
||||||
CardEdition upcomingSet = null;
|
CardEdition upcomingSet = null;
|
||||||
Date today = new Date();
|
Date today = new Date();
|
||||||
List<String> skippedCardName = new ArrayList<>();
|
|
||||||
|
|
||||||
for (CardEdition e : editions.getOrderedEditions()) {
|
for (CardEdition e : editions.getOrderedEditions()) {
|
||||||
boolean coreOrExpSet = e.getType() == CardEdition.Type.CORE || e.getType() == CardEdition.Type.EXPANSION;
|
boolean coreOrExpSet = e.getType() == CardEdition.Type.CORE || e.getType() == CardEdition.Type.EXPANSION;
|
||||||
boolean isCoreExpSet = coreOrExpSet || e.getType() == CardEdition.Type.REPRINT;
|
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) {
|
if (logMissingPerEdition && isCoreExpSet) {
|
||||||
System.out.print(e.getName() + " (" + e.getAllCardsInSet().size() + " cards)");
|
System.out.print(e.getName() + " (" + e.getAllCardsInSet().size() + " cards)");
|
||||||
}
|
}
|
||||||
if (coreOrExpSet && e.getDate().after(today) && upcomingSet == null) {
|
if (coreOrExpSet && e.getDate().after(today) && upcomingSet == null) {
|
||||||
if (skip)
|
|
||||||
upcomingSet = e;
|
upcomingSet = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
|
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
|
||||||
CardRules cr = rulesByName.get(cis.name);
|
CardRules cr = rulesByName.get(cis.name);
|
||||||
if (cr != null && !cr.getType().isBasicLand() && skip) {
|
|
||||||
skippedCardName.add(cis.name);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cr != null) {
|
if (cr != null) {
|
||||||
addSetCard(e, cis, cr);
|
addSetCard(e, cis, cr);
|
||||||
}
|
}
|
||||||
@@ -244,7 +245,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
if (!contains(cr.getName())) {
|
if (!contains(cr.getName())) {
|
||||||
if (upcomingSet != null) {
|
if (upcomingSet != null) {
|
||||||
addCard(new PaperCard(cr, upcomingSet.getCode(), CardRarity.Unknown, 1));
|
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. ");
|
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));
|
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) {
|
public void addCard(PaperCard paperCard) {
|
||||||
|
if (excludeCard(paperCard.getName(), paperCard.getEdition()))
|
||||||
|
return;
|
||||||
|
|
||||||
allCardsByName.put(paperCard.getName(), paperCard);
|
allCardsByName.put(paperCard.getName(), paperCard);
|
||||||
|
|
||||||
if (paperCard.getRules().getSplitType() == CardSplitType.None) { return; }
|
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);
|
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() {
|
private void reIndex() {
|
||||||
uniqueCardsByName.clear();
|
uniqueCardsByName.clear();
|
||||||
for (Entry<String, Collection<PaperCard>> kv : allCardsByName.asMap().entrySet()) {
|
for (Entry<String, Collection<PaperCard>> kv : getAllCardsByName().asMap().entrySet()) {
|
||||||
uniqueCardsByName.put(kv.getKey(), getFirstWithImage(kv.getValue()));
|
PaperCard pc = getFirstWithImage(kv.getValue());
|
||||||
|
uniqueCardsByName.put(kv.getKey(), pc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,6 +576,17 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
return uniqueCardsByName.values();
|
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) {
|
public PaperCard getUniqueByName(final String name) {
|
||||||
return uniqueCardsByName.get(getName(name));
|
return uniqueCardsByName.get(getName(name));
|
||||||
}
|
}
|
||||||
@@ -575,11 +601,20 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<PaperCard> getAllCards() {
|
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() {
|
public Collection<PaperCard> getAllNonPromoCards() {
|
||||||
return Lists.newArrayList(Iterables.filter(this.roAllCards, new Predicate<PaperCard>() {
|
return Lists.newArrayList(Iterables.filter(getAllCards(), new Predicate<PaperCard>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(final PaperCard paperCard) {
|
public boolean apply(final PaperCard paperCard) {
|
||||||
CardEdition edition = null;
|
CardEdition edition = null;
|
||||||
@@ -602,13 +637,27 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PaperCard> getAllCards(String cardName) {
|
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 */
|
/** Returns a modifiable list of cards matching the given predicate */
|
||||||
@Override
|
@Override
|
||||||
public List<PaperCard> getAllCards(Predicate<PaperCard> predicate) {
|
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?
|
// Do I want a foiled version of these cards?
|
||||||
@@ -630,12 +679,12 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(String name) {
|
public boolean contains(String name) {
|
||||||
return allCardsByName.containsKey(getName(name));
|
return getAllCardsByName().containsKey(getName(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<PaperCard> iterator() {
|
public Iterator<PaperCard> iterator() {
|
||||||
return this.roAllCards.iterator();
|
return getAllCards().iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Predicate<? super PaperCard> wasPrintedInSets(List<String> setCodes) {
|
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
|
// 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
|
// commonly used printsheets with collector number
|
||||||
public enum EditionSectionWithCollectorNumbers {
|
public enum EditionSectionWithCollectorNumbers {
|
||||||
@@ -127,7 +127,8 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
|||||||
EXTENDED_ART("extended art"),
|
EXTENDED_ART("extended art"),
|
||||||
ALTERNATE_ART("alternate art"),
|
ALTERNATE_ART("alternate art"),
|
||||||
BUY_A_BOX("buy a box"),
|
BUY_A_BOX("buy a box"),
|
||||||
PROMO("promo");
|
PROMO("promo"),
|
||||||
|
BOX_TOPPER("box topper");
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package forge.card;
|
package forge.card;
|
||||||
|
|
||||||
import java.util.StringTokenizer;
|
import java.util.*;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
@@ -28,6 +28,9 @@ import forge.card.mana.ManaCost;
|
|||||||
import forge.card.mana.ManaCostShard;
|
import forge.card.mana.ManaCostShard;
|
||||||
import forge.util.TextUtil;
|
import forge.util.TextUtil;
|
||||||
|
|
||||||
|
import static forge.card.MagicColor.Constant.*;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A collection of methods containing full
|
* A collection of methods containing full
|
||||||
* meta and gameplay properties of a card.
|
* meta and gameplay properties of a card.
|
||||||
@@ -42,6 +45,7 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
private ICardFace otherPart;
|
private ICardFace otherPart;
|
||||||
private CardAiHints aiHints;
|
private CardAiHints aiHints;
|
||||||
private ColorSet colorIdentity;
|
private ColorSet colorIdentity;
|
||||||
|
private ColorSet deckbuildingColors;
|
||||||
private String meldWith;
|
private String meldWith;
|
||||||
private String partnerWith;
|
private String partnerWith;
|
||||||
|
|
||||||
@@ -581,4 +585,25 @@ public final class CardRules implements ICardCharacteristics {
|
|||||||
}
|
}
|
||||||
return null;
|
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 {
|
public enum Supertype {
|
||||||
Basic,
|
Basic,
|
||||||
Elite,
|
Elite,
|
||||||
|
Host,
|
||||||
Legendary,
|
Legendary,
|
||||||
Snow,
|
Snow,
|
||||||
Ongoing,
|
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> 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 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>()
|
public static final ImmutableMap<String, String> ANY_COLOR_CONVERSION = new ImmutableMap.Builder<String, String>()
|
||||||
.put("ManaColorConversion", "Additive")
|
.put("ManaConversion", "AnyType->AnyColor")
|
||||||
.put("WhiteConversion", "Color")
|
|
||||||
.put("BlueConversion", "Color")
|
|
||||||
.put("BlackConversion", "Color")
|
|
||||||
.put("RedConversion", "Color")
|
|
||||||
.put("GreenConversion", "Color")
|
|
||||||
.put("ColorlessConversion", "Color")
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static final ImmutableMap<String, String> ANY_TYPE_CONVERSION = new ImmutableMap.Builder<String, String>()
|
public static final ImmutableMap<String, String> ANY_TYPE_CONVERSION = new ImmutableMap.Builder<String, String>()
|
||||||
.put("ManaColorConversion", "Additive")
|
.put("ManaConversion", "AnyType->AnyType")
|
||||||
.put("WhiteConversion", "Type")
|
|
||||||
.put("BlueConversion", "Type")
|
|
||||||
.put("BlackConversion", "Type")
|
|
||||||
.put("RedConversion", "Type")
|
|
||||||
.put("GreenConversion", "Type")
|
|
||||||
.put("ColorlessConversion", "Type")
|
|
||||||
.build();
|
.build();
|
||||||
/**
|
/**
|
||||||
* Private constructor to prevent instantiation.
|
* Private constructor to prevent instantiation.
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ public class PrintSheet {
|
|||||||
|
|
||||||
for(CardEdition edition : editions) {
|
for(CardEdition edition : editions) {
|
||||||
for(PrintSheet ps : edition.getPrintSheetsBySection()) {
|
for(PrintSheet ps : edition.getPrintSheetsBySection()) {
|
||||||
System.out.println(ps.name);
|
|
||||||
sheets.add(ps.name, ps);
|
sheets.add(ps.name, ps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,18 @@ public abstract class ManaAtom {
|
|||||||
return 0; // generic
|
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){
|
public static int getIndexOfFirstManaType(final byte color){
|
||||||
for (int i = 0; i < MANATYPES.length; i++) {
|
for (int i = 0; i < MANATYPES.length; i++) {
|
||||||
if ((color & MANATYPES[i]) != 0) {
|
if ((color & MANATYPES[i]) != 0) {
|
||||||
|
|||||||
@@ -64,7 +64,10 @@ public enum ManaCostShard {
|
|||||||
PR(ManaAtom.RED | ManaAtom.OR_2_LIFE, "P/R", "PR"),
|
PR(ManaAtom.RED | ManaAtom.OR_2_LIFE, "P/R", "PR"),
|
||||||
PG(ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "P/G", "PG"),
|
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;
|
private final int shard;
|
||||||
|
|
||||||
|
|||||||
@@ -489,15 +489,6 @@ public abstract class DeckGeneratorBase {
|
|||||||
return dLands;
|
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) {
|
private void addCardNameToList(String cardName, List<String> cardNameList) {
|
||||||
if (pool.contains(cardName)) { //avoid adding card if it's not in pool
|
if (pool.contains(cardName)) { //avoid adding card if it's not in pool
|
||||||
cardNameList.add(cardName);
|
cardNameList.add(cardName);
|
||||||
|
|||||||
@@ -113,8 +113,12 @@ public class CardTranslation {
|
|||||||
|
|
||||||
public static void buildOracleMapping(String faceName, String oracleText) {
|
public static void buildOracleMapping(String faceName, String oracleText) {
|
||||||
if (!needsTranslation() || oracleMappings.containsKey(faceName)) return;
|
if (!needsTranslation() || oracleMappings.containsKey(faceName)) return;
|
||||||
String translatedName = getTranslatedName(faceName);
|
|
||||||
String translatedText = getTranslatedOracle(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<>();
|
List <Pair <String, String> > mapping = new ArrayList<>();
|
||||||
String [] splitOracleText = oracleText.split("\\\\n");
|
String [] splitOracleText = oracleText.split("\\\\n");
|
||||||
String [] splitTranslatedText = translatedText.split("\r\n\r\n");
|
String [] splitTranslatedText = translatedText.split("\r\n\r\n");
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ public class ImageUtil {
|
|||||||
|
|
||||||
key = key.substring(2);
|
key = key.substring(2);
|
||||||
PaperCard cp = StaticData.instance().getCommonCards().getCard(key);
|
PaperCard cp = StaticData.instance().getCommonCards().getCard(key);
|
||||||
|
if (cp == null) {
|
||||||
|
cp = StaticData.instance().getCustomCards().getCard(key);
|
||||||
|
}
|
||||||
if (cp == null) {
|
if (cp == null) {
|
||||||
cp = StaticData.instance().getVariantCards().getCard(key);
|
cp = StaticData.instance().getVariantCards().getCard(key);
|
||||||
}
|
}
|
||||||
@@ -73,6 +76,8 @@ public class ImageUtil {
|
|||||||
|
|
||||||
if (includeSet) {
|
if (includeSet) {
|
||||||
String editionAliased = isDownloadUrl ? StaticData.instance().getEditions().getCode2ByCode(edition) : ImageKeys.getSetFolder(edition);
|
String editionAliased = isDownloadUrl ? StaticData.instance().getEditions().getCode2ByCode(edition) : ImageKeys.getSetFolder(edition);
|
||||||
|
if (editionAliased == "") //FIXME: Custom Cards Workaround
|
||||||
|
editionAliased = edition;
|
||||||
return TextUtil.concatNoSpace(editionAliased, "/", fname);
|
return TextUtil.concatNoSpace(editionAliased, "/", fname);
|
||||||
} else {
|
} else {
|
||||||
return fname;
|
return fname;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>forge</artifactId>
|
<artifactId>forge</artifactId>
|
||||||
<groupId>forge</groupId>
|
<groupId>forge</groupId>
|
||||||
<version>1.6.40-SNAPSHOT</version>
|
<version>1.6.40</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>forge-game</artifactId>
|
<artifactId>forge-game</artifactId>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package forge.game;
|
package forge.game;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
@@ -162,15 +162,23 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
|||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public boolean matchesValid(final Object o, final String[] valids, final Card srcCard) {
|
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) {
|
if (o instanceof GameObject) {
|
||||||
final GameObject c = (GameObject) o;
|
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<?>) {
|
} else if (o instanceof Iterable<?>) {
|
||||||
for (Object o2 : (Iterable<?>)o) {
|
for (Object o2 : (Iterable<?>)o) {
|
||||||
if (matchesValid(o2, valids, srcCard)) {
|
if (matchesValid(o2, valids, srcCard, srcPlayer)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (o instanceof String) {
|
||||||
|
if (ArrayUtils.contains(valids, o)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -522,11 +530,16 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> getSVars() {
|
public Map<String, String> getSVars() {
|
||||||
Map<String, String> res = new HashMap<>(getSVarFallback().getSVars());
|
Map<String, String> res = Maps.newHashMap(getSVarFallback().getSVars());
|
||||||
res.putAll(sVars);
|
res.putAll(sVars);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getDirectSVars() {
|
||||||
|
return sVars;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSVars(Map<String, String> newSVars) {
|
public void setSVars(Map<String, String> newSVars) {
|
||||||
sVars = Maps.newTreeMap();
|
sVars = Maps.newTreeMap();
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package forge.game;
|
|||||||
|
|
||||||
import forge.card.ColorSet;
|
import forge.card.ColorSet;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
|
import forge.card.mana.ManaAtom;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardState;
|
import forge.game.card.CardState;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
@@ -189,6 +191,25 @@ public class ForgeScript {
|
|||||||
if (!Expressions.compare(y, property, x)) {
|
if (!Expressions.compare(y, property, x)) {
|
||||||
return false;
|
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) {
|
} else if (sa.getHostCard() != null) {
|
||||||
return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility);
|
return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -769,21 +769,31 @@ public class Game {
|
|||||||
p.revealFaceDownCards();
|
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())) {
|
if (c.getController().equals(p) && (c.isPlane() || c.isPhenomenon())) {
|
||||||
planarControllerLost = true;
|
planarControllerLost = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isMultiplayer) {
|
if (isMultiplayer) {
|
||||||
// unattach all "Enchant Player"
|
// unattach all "Enchant Player"
|
||||||
c.removeAttachedTo(p);
|
c.removeAttachedTo(p);
|
||||||
if (c.getOwner().equals(p)) {
|
if (c.getOwner().equals(p)) {
|
||||||
for(Card cc : cards) {
|
for (Card cc : cards) {
|
||||||
cc.removeImprintedCard(c);
|
cc.removeImprintedCard(c);
|
||||||
cc.removeEncodedCard(c);
|
cc.removeEncodedCard(c);
|
||||||
cc.removeRemembered(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 {
|
} else {
|
||||||
// return stolen permanents
|
// return stolen permanents
|
||||||
if (c.getController().equals(p) && c.isInZone(ZoneType.Battlefield)) {
|
if (c.getController().equals(p) && c.isInZone(ZoneType.Battlefield)) {
|
||||||
@@ -822,6 +832,8 @@ public class Game {
|
|||||||
// Remove leftover items from
|
// Remove leftover items from
|
||||||
this.getStack().removeInstancesControlledBy(p);
|
this.getStack().removeInstancesControlledBy(p);
|
||||||
|
|
||||||
|
getTriggerHandler().onPlayerLost(p);
|
||||||
|
|
||||||
ingamePlayers.remove(p);
|
ingamePlayers.remove(p);
|
||||||
lostPlayers.add(p);
|
lostPlayers.add(p);
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ public class GameAction {
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
if (zoneFrom == null && !c.isToken()) {
|
if (zoneFrom == null && !c.isToken()) {
|
||||||
zoneTo.add(c, position);
|
zoneTo.add(c, position, CardUtil.getLKICopy(c));
|
||||||
checkStaticAbilities();
|
checkStaticAbilities();
|
||||||
game.getTriggerHandler().registerActiveTrigger(c, true);
|
game.getTriggerHandler().registerActiveTrigger(c, true);
|
||||||
game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo));
|
game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo));
|
||||||
@@ -251,6 +251,7 @@ public class GameAction {
|
|||||||
copied.copyChangedTextFrom(c);
|
copied.copyChangedTextFrom(c);
|
||||||
|
|
||||||
// copy exiled properties when adding to stack
|
// copy exiled properties when adding to stack
|
||||||
|
// will be cleanup later in MagicStack
|
||||||
copied.setExiledWith(c.getExiledWith());
|
copied.setExiledWith(c.getExiledWith());
|
||||||
copied.setExiledBy(c.getExiledBy());
|
copied.setExiledBy(c.getExiledBy());
|
||||||
|
|
||||||
@@ -416,13 +417,7 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!zoneTo.is(ZoneType.Exile) && !zoneTo.is(ZoneType.Stack)) {
|
if (!zoneTo.is(ZoneType.Exile) && !zoneTo.is(ZoneType.Stack)) {
|
||||||
Card with = c.getExiledWith();
|
c.cleanupExiledWith();
|
||||||
if (with != null) {
|
|
||||||
with.removeUntilLeavesBattlefield(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
c.setExiledWith(null);
|
|
||||||
c.setExiledBy(null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,7 +471,7 @@ public class GameAction {
|
|||||||
if (card == c) {
|
if (card == c) {
|
||||||
zoneTo.add(copied, position, lastKnownInfo); // the modified state of the card is also reported here (e.g. for Morbid + Awaken)
|
zoneTo.add(copied, position, lastKnownInfo); // the modified state of the card is also reported here (e.g. for Morbid + Awaken)
|
||||||
} else {
|
} else {
|
||||||
zoneTo.add(card, position);
|
zoneTo.add(card, position, CardUtil.getLKICopy(card));
|
||||||
}
|
}
|
||||||
card.setZone(zoneTo);
|
card.setZone(zoneTo);
|
||||||
}
|
}
|
||||||
@@ -603,8 +598,6 @@ public class GameAction {
|
|||||||
copied.setState(CardStateName.Original, true);
|
copied.setState(CardStateName.Original, true);
|
||||||
}
|
}
|
||||||
unattachCardLeavingBattlefield(copied);
|
unattachCardLeavingBattlefield(copied);
|
||||||
// Remove all changed keywords
|
|
||||||
copied.removeAllChangedText(game.getNextTimestamp());
|
|
||||||
} else if (toBattlefield) {
|
} else if (toBattlefield) {
|
||||||
// reset timestamp in changezone effects so they have same timestamp if ETB simutaneously
|
// reset timestamp in changezone effects so they have same timestamp if ETB simutaneously
|
||||||
copied.setTimestamp(game.getNextTimestamp());
|
copied.setTimestamp(game.getNextTimestamp());
|
||||||
@@ -859,7 +852,24 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final Card exile(final Card c, SpellAbility cause) {
|
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) {
|
public final Card exile(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
|
||||||
if (game.isCardExiled(c)) {
|
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.
|
// Temporarily disable (if mode = true) actively checking static abilities.
|
||||||
private void setHoldCheckingStaticAbilities(boolean mode) {
|
private void setHoldCheckingStaticAbilities(boolean mode) {
|
||||||
holdCheckingStaticAbilities = mode;
|
holdCheckingStaticAbilities = mode;
|
||||||
@@ -1245,6 +1282,7 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((game.getRules().hasAppliedVariant(GameType.Commander)
|
if ((game.getRules().hasAppliedVariant(GameType.Commander)
|
||||||
|
|| game.getRules().hasAppliedVariant(GameType.Brawl)
|
||||||
|| game.getRules().hasAppliedVariant(GameType.Planeswalker)) && !checkAgain) {
|
|| game.getRules().hasAppliedVariant(GameType.Planeswalker)) && !checkAgain) {
|
||||||
Iterable<Card> cards = p.getCardsIn(ZoneType.Graveyard).threadSafeIterable();
|
Iterable<Card> cards = p.getCardsIn(ZoneType.Graveyard).threadSafeIterable();
|
||||||
for (final Card c : cards) {
|
for (final Card c : cards) {
|
||||||
@@ -1266,7 +1304,7 @@ public class GameAction {
|
|||||||
if (game.getCombat() != null) {
|
if (game.getCombat() != null) {
|
||||||
game.getCombat().removeAbsentCombatants();
|
game.getCombat().removeAbsentCombatants();
|
||||||
}
|
}
|
||||||
table.triggerChangesZoneAll(game);
|
table.triggerChangesZoneAll(game, null);
|
||||||
if (!checkAgain) {
|
if (!checkAgain) {
|
||||||
break; // do not continue the loop
|
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
|
//check initial hand
|
||||||
List<Card> lib1 = Lists.newArrayList(p1.getZone(ZoneType.Library).getCards().threadSafeIterable());
|
List<Card> lib1 = Lists.newArrayList(p1.getZone(ZoneType.Library).getCards().threadSafeIterable());
|
||||||
List<Card> hand1 = lib1.subList(0,p1.getMaxHandSize());
|
List<Card> hand1 = lib1.subList(0,p1.getMaxHandSize());
|
||||||
System.out.println(hand1.toString());
|
|
||||||
|
|
||||||
//shuffle
|
//shuffle
|
||||||
List<Card> shuffledCards = Lists.newArrayList(p1.getZone(ZoneType.Library).getCards().threadSafeIterable());
|
List<Card> shuffledCards = Lists.newArrayList(p1.getZone(ZoneType.Library).getCards().threadSafeIterable());
|
||||||
@@ -1743,33 +1779,32 @@ public class GameAction {
|
|||||||
|
|
||||||
//check a second hand
|
//check a second hand
|
||||||
List<Card> hand2 = shuffledCards.subList(0,p1.getMaxHandSize());
|
List<Card> hand2 = shuffledCards.subList(0,p1.getMaxHandSize());
|
||||||
System.out.println(hand2.toString());
|
|
||||||
|
|
||||||
//choose better hand according to land count
|
//choose better hand according to land count
|
||||||
float averageLandRatio = getLandRatio(lib1);
|
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.getZone(ZoneType.Library).setCards(shuffledCards);
|
||||||
}
|
}
|
||||||
p1.drawCards(p1.getMaxHandSize());
|
p1.drawCards(p1.getMaxHandSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
private float getLandRatio(List<Card> deck){
|
private float getLandRatio(List<Card> deck) {
|
||||||
int landCount = 0;
|
int landCount = 0;
|
||||||
for(Card c:deck){
|
for (Card c:deck) {
|
||||||
if(c.isLand()){
|
if (c.isLand()){
|
||||||
landCount++;
|
landCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (landCount == 0 ){
|
if(landCount == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return Float.valueOf(landCount)/Float.valueOf(deck.size());
|
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;
|
int landCount = 0;
|
||||||
for(Card c:hand){
|
for (Card c:hand) {
|
||||||
if(c.isLand()){
|
if (c.isLand()) {
|
||||||
landCount++;
|
landCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,14 +41,12 @@ import forge.game.spellability.TargetRestrictions;
|
|||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.collect.FCollection;
|
|
||||||
|
|
||||||
|
|
||||||
public abstract class GameEntity extends GameObject implements IIdentifiable {
|
public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||||
protected final int id;
|
protected final int id;
|
||||||
private String name = "";
|
private String name = "";
|
||||||
private int preventNextDamage = 0;
|
private int preventNextDamage = 0;
|
||||||
protected CardCollection attachedCards;
|
protected CardCollection attachedCards = new CardCollection();
|
||||||
private Map<Card, Map<String, String>> preventionShieldsWithEffects = Maps.newTreeMap();
|
private Map<Card, Map<String, String>> preventionShieldsWithEffects = Maps.newTreeMap();
|
||||||
protected Map<CounterType, Integer> counters = Maps.newHashMap();
|
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 abstract boolean hasKeyword(final Keyword keyword);
|
||||||
|
|
||||||
public final CardCollectionView getEnchantedBy() {
|
public final CardCollectionView getEnchantedBy() {
|
||||||
if (attachedCards == null) {
|
|
||||||
return CardCollection.EMPTY;
|
|
||||||
}
|
|
||||||
// enchanted means attached by Aura
|
// 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() {
|
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) {
|
public final void setAttachedCards(final Iterable<Card> cards) {
|
||||||
if (cards == null) {
|
|
||||||
attachedCards = null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
attachedCards = new CardCollection(cards);
|
attachedCards = new CardCollection(cards);
|
||||||
}
|
updateAttachedCards();
|
||||||
|
|
||||||
getView().updateAttachedCards(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean hasCardAttachments() {
|
public final boolean hasCardAttachments() {
|
||||||
return FCollection.hasElements(attachedCards);
|
return !getAttachedCards().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isEnchanted() {
|
public final boolean isEnchanted() {
|
||||||
if (attachedCards == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// enchanted means attached by Aura
|
// 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) {
|
public final boolean hasCardAttachment(Card c) {
|
||||||
return FCollection.hasElement(attachedCards, c);
|
return getAttachedCards().contains(c);
|
||||||
}
|
}
|
||||||
public final boolean isEnchantedBy(Card c) {
|
public final boolean isEnchantedBy(Card c) {
|
||||||
// Rule 303.4k Even if c is no Aura it still counts
|
// 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) {
|
public final boolean hasCardAttachment(final String cardName) {
|
||||||
if (attachedCards == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return CardLists.count(getAttachedCards(), CardPredicates.nameEquals(cardName)) > 0;
|
return CardLists.count(getAttachedCards(), CardPredicates.nameEquals(cardName)) > 0;
|
||||||
}
|
}
|
||||||
public final boolean isEnchantedBy(final String cardName) {
|
public final boolean isEnchantedBy(final String cardName) {
|
||||||
@@ -344,12 +332,8 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
* @param Card c
|
* @param Card c
|
||||||
*/
|
*/
|
||||||
public final void addAttachedCard(final Card c) {
|
public final void addAttachedCard(final Card c) {
|
||||||
if (attachedCards == null) {
|
|
||||||
attachedCards = new CardCollection();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attachedCards.add(c)) {
|
if (attachedCards.add(c)) {
|
||||||
getView().updateAttachedCards(this);
|
updateAttachedCards();
|
||||||
getGame().fireEvent(new GameEventCardAttachment(c, null, this));
|
getGame().fireEvent(new GameEventCardAttachment(c, null, this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,17 +343,16 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
|||||||
* @param Card c
|
* @param Card c
|
||||||
*/
|
*/
|
||||||
public final void removeAttachedCard(final Card c) {
|
public final void removeAttachedCard(final Card c) {
|
||||||
if (attachedCards == null) { return; }
|
|
||||||
|
|
||||||
if (attachedCards.remove(c)) {
|
if (attachedCards.remove(c)) {
|
||||||
if (attachedCards.isEmpty()) {
|
updateAttachedCards();
|
||||||
attachedCards = null;
|
|
||||||
}
|
|
||||||
getView().updateAttachedCards(this);
|
|
||||||
getGame().fireEvent(new GameEventCardAttachment(c, this, null));
|
getGame().fireEvent(new GameEventCardAttachment(c, this, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final void updateAttachedCards() {
|
||||||
|
getView().updateAttachedCards(this);
|
||||||
|
}
|
||||||
|
|
||||||
public final void unAttachAllCards() {
|
public final void unAttachAllCards() {
|
||||||
for (Card c : Lists.newArrayList(getAttachedCards())) {
|
for (Card c : Lists.newArrayList(getAttachedCards())) {
|
||||||
c.unattachFromEntity(this);
|
c.unattachFromEntity(this);
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package forge.game;
|
|||||||
|
|
||||||
import java.util.Map;
|
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.ForwardingTable;
|
||||||
import com.google.common.collect.HashBasedTable;
|
import com.google.common.collect.HashBasedTable;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
@@ -10,49 +13,61 @@ import com.google.common.collect.Table;
|
|||||||
import forge.game.ability.AbilityKey;
|
import forge.game.ability.AbilityKey;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
|
import forge.game.player.Player;
|
||||||
import forge.game.trigger.TriggerType;
|
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)
|
* (non-Javadoc)
|
||||||
* @see com.google.common.collect.ForwardingTable#delegate()
|
* @see com.google.common.collect.ForwardingTable#delegate()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected Table<GameEntity, CounterType, Integer> delegate() {
|
protected Table<Optional<Player>, GameEntity, Map<CounterType, Integer>> delegate() {
|
||||||
return dataMap;
|
return dataMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
public Integer put(Player putter, GameEntity object, CounterType type, Integer value) {
|
||||||
* @see com.google.common.collect.ForwardingTable#put(java.lang.Object, java.lang.Object, java.lang.Object)
|
Optional<Player> o = Optional.fromNullable(putter);
|
||||||
*/
|
Map<CounterType, Integer> map = get(o, object);
|
||||||
@Override
|
if (map == null) {
|
||||||
public Integer put(GameEntity rowKey, CounterType columnKey, Integer value) {
|
map = Maps.newHashMap();
|
||||||
return super.put(rowKey, columnKey, get(rowKey, columnKey) + value);
|
put(o, object, map);
|
||||||
|
}
|
||||||
|
return map.put(type, ObjectUtils.firstNonNull(map.get(type), 0) + value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int get(Player putter, GameEntity object, CounterType type) {
|
||||||
@Override
|
Optional<Player> o = Optional.fromNullable(putter);
|
||||||
public Integer get(Object rowKey, Object columnKey) {
|
Map<CounterType, Integer> map = get(o, object);
|
||||||
if (!contains(rowKey, columnKey)) {
|
if (map == null || !map.containsKey(type)) {
|
||||||
return 0; // helper to not return null value
|
return 0;
|
||||||
}
|
}
|
||||||
return super.get(rowKey, columnKey);
|
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
|
* returns the counters that can still be removed from game entity
|
||||||
*/
|
*/
|
||||||
public Map<CounterType, Integer> filterToRemove(GameEntity ge) {
|
public Map<CounterType, Integer> filterToRemove(GameEntity ge) {
|
||||||
Map<CounterType, Integer> result = Maps.newHashMap();
|
Map<CounterType, Integer> result = Maps.newHashMap();
|
||||||
if (!containsRow(ge)) {
|
if (!containsColumn(ge)) {
|
||||||
result.putAll(ge.getCounters());
|
result.putAll(ge.getCounters());
|
||||||
return result;
|
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()) {
|
for (Map.Entry<CounterType, Integer> e : ge.getCounters().entrySet()) {
|
||||||
Integer rest = e.getValue() - (alreadyRemoved.containsKey(e.getKey()) ? alreadyRemoved.get(e.getKey()) : 0);
|
Integer rest = e.getValue() - (alreadyRemoved.containsKey(e.getKey()) ? alreadyRemoved.get(e.getKey()) : 0);
|
||||||
if (rest > 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) {
|
public Map<GameEntity, Integer> filterTable(CounterType type, String valid, Card host, CardTraitBase sa) {
|
||||||
Map<GameEntity, Integer> result = Maps.newHashMap();
|
Map<GameEntity, Integer> result = Maps.newHashMap();
|
||||||
|
|
||||||
for (Map.Entry<GameEntity, Integer> e : column(type).entrySet()) {
|
for (Map.Entry<GameEntity, Map<Optional<Player>, Map<CounterType, Integer>>> gm : columnMap().entrySet()) {
|
||||||
if (e.getValue() > 0 && e.getKey().isValid(valid, host.getController(), host, sa)) {
|
if (gm.getKey().isValid(valid, host.getController(), host, sa)) {
|
||||||
result.put(e.getKey(), e.getValue());
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void triggerCountersPutAll(final Game game) {
|
public void triggerCountersPutAll(final Game game) {
|
||||||
if (!isEmpty()) {
|
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();
|
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||||
runParams.put(AbilityKey.Objects, this);
|
runParams.put(AbilityKey.Objects, this);
|
||||||
game.getTriggerHandler().runTrigger(TriggerType.CounterAddedAll, runParams, false);
|
game.getTriggerHandler().runTrigger(TriggerType.CounterAddedAll, runParams, false);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.game;
|
package forge.game;
|
||||||
|
|
||||||
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardView;
|
import forge.game.card.CardView;
|
||||||
import forge.trackable.TrackableCollection;
|
import forge.trackable.TrackableCollection;
|
||||||
import forge.trackable.TrackableObject;
|
import forge.trackable.TrackableObject;
|
||||||
@@ -54,6 +55,12 @@ public abstract class GameEntityView extends TrackableObject {
|
|||||||
public boolean hasCardAttachments() {
|
public boolean hasCardAttachments() {
|
||||||
return getAttachedCards() != null;
|
return getAttachedCards() != null;
|
||||||
}
|
}
|
||||||
|
public Iterable<CardView> getAllAttachedCards() {
|
||||||
|
return get(TrackableProperty.AllAttachedCards);
|
||||||
|
}
|
||||||
|
public boolean hasAnyCardAttachments() {
|
||||||
|
return getAllAttachedCards() != null;
|
||||||
|
}
|
||||||
|
|
||||||
protected void updateAttachedCards(GameEntity e) {
|
protected void updateAttachedCards(GameEntity e) {
|
||||||
if (e.hasCardAttachments()) {
|
if (e.hasCardAttachments()) {
|
||||||
@@ -62,5 +69,11 @@ public abstract class GameEntityView extends TrackableObject {
|
|||||||
else {
|
else {
|
||||||
set(TrackableProperty.AttachedCards, null);
|
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()) {
|
if (!this.allowedSetCodes_ro.isEmpty()) {
|
||||||
p = Predicates.and(p, printed ?
|
p = Predicates.and(p, printed ?
|
||||||
IPaperCard.Predicates.printedInSets(this.allowedSetCodes_ro, 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()) {
|
if (!this.allowedRarities.isEmpty()) {
|
||||||
List<Predicate<? super PaperCard>> crp = Lists.newArrayList();
|
List<Predicate<? super PaperCard>> crp = Lists.newArrayList();
|
||||||
@@ -302,6 +302,7 @@ public class GameFormat implements Comparable<GameFormat> {
|
|||||||
{
|
{
|
||||||
coreFormats.add("Standard.txt");
|
coreFormats.add("Standard.txt");
|
||||||
coreFormats.add("Pioneer.txt");
|
coreFormats.add("Pioneer.txt");
|
||||||
|
coreFormats.add("Historic.txt");
|
||||||
coreFormats.add("Modern.txt");
|
coreFormats.add("Modern.txt");
|
||||||
coreFormats.add("Legacy.txt");
|
coreFormats.add("Legacy.txt");
|
||||||
coreFormats.add("Vintage.txt");
|
coreFormats.add("Vintage.txt");
|
||||||
@@ -484,6 +485,10 @@ public class GameFormat implements Comparable<GameFormat> {
|
|||||||
return this.map.get("Pioneer");
|
return this.map.get("Pioneer");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GameFormat getHistoric() {
|
||||||
|
return this.map.get("Historic");
|
||||||
|
}
|
||||||
|
|
||||||
public GameFormat getModern() {
|
public GameFormat getModern() {
|
||||||
return this.map.get("Modern");
|
return this.map.get("Modern");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ package forge.game;
|
|||||||
public enum GlobalRuleChange {
|
public enum GlobalRuleChange {
|
||||||
|
|
||||||
alwaysWither ("All damage is dealt as though its source had wither."),
|
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."),
|
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."),
|
manapoolsDontEmpty ("Mana pools don't empty as steps and phases end."),
|
||||||
noCreatureETBTriggers ("Creatures entering the battlefield don't cause abilities to trigger."),
|
noCreatureETBTriggers ("Creatures entering the battlefield don't cause abilities to trigger."),
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public interface IHasSVars {
|
|||||||
//public Set<String> getSVars();
|
//public Set<String> getSVars();
|
||||||
|
|
||||||
public Map<String, String> getSVars();
|
public Map<String, String> getSVars();
|
||||||
|
public Map<String, String> getDirectSVars();
|
||||||
|
|
||||||
public void removeSVar(final String var);
|
public void removeSVar(final String var);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ public class StaticEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remove abilities
|
// remove abilities
|
||||||
if (hasParam("AddAbility") || hasParam("GainsAbilitiesOf")
|
if (hasParam("AddAbility") || hasParam("GainsAbilitiesOf") || hasParam("GainsAbilitiesOfDefined")
|
||||||
|| hasParam("AddTrigger") || hasParam("AddStaticAbility") || hasParam("AddReplacementEffects")
|
|| hasParam("AddTrigger") || hasParam("AddStaticAbility") || hasParam("AddReplacementEffects")
|
||||||
|| hasParam("RemoveAllAbilities") || hasParam("RemoveLandTypes")) {
|
|| hasParam("RemoveAllAbilities") || hasParam("RemoveLandTypes")) {
|
||||||
affectedCard.removeChangedCardTraits(getTimestamp());
|
affectedCard.removeChangedCardTraits(getTimestamp());
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ public final class AbilityFactory {
|
|||||||
|
|
||||||
static final List<String> additionalAbilityKeys = Lists.newArrayList(
|
static final List<String> additionalAbilityKeys = Lists.newArrayList(
|
||||||
"WinSubAbility", "OtherwiseSubAbility", // Clash
|
"WinSubAbility", "OtherwiseSubAbility", // Clash
|
||||||
|
"BidSubAbility", // BidLifeEffect
|
||||||
"ChooseNumberSubAbility", "Lowest", "Highest", "NotLowest", // ChooseNumber
|
"ChooseNumberSubAbility", "Lowest", "Highest", "NotLowest", // ChooseNumber
|
||||||
"HeadsSubAbility", "TailsSubAbility", "LoseSubAbility", // FlipCoin
|
"HeadsSubAbility", "TailsSubAbility", "LoseSubAbility", // FlipCoin
|
||||||
"TrueSubAbility", "FalseSubAbility", // Branch
|
"TrueSubAbility", "FalseSubAbility", // Branch
|
||||||
@@ -272,7 +273,7 @@ public final class AbilityFactory {
|
|||||||
|
|
||||||
for (final String key : additionalAbilityKeys) {
|
for (final String key : additionalAbilityKeys) {
|
||||||
if (mapParams.containsKey(key) && spellAbility.getAdditionalAbility(key) == null) {
|
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"),
|
Cause("Cause"),
|
||||||
Causer("Causer"),
|
Causer("Causer"),
|
||||||
Championed("Championed"),
|
Championed("Championed"),
|
||||||
CopySA("CopySA"),
|
|
||||||
Cost("Cost"),
|
Cost("Cost"),
|
||||||
CostStack("CostStack"),
|
CostStack("CostStack"),
|
||||||
CounterAmount("CounterAmount"),
|
CounterAmount("CounterAmount"),
|
||||||
CounteredSA("CounteredSA"),
|
CounteredSA("CounteredSA"),
|
||||||
CounterNum("CounterNum"),
|
CounterNum("CounterNum"),
|
||||||
|
CounterMap("CounterMap"),
|
||||||
CounterTable("CounterTable"),
|
CounterTable("CounterTable"),
|
||||||
CounterType("CounterType"),
|
CounterType("CounterType"),
|
||||||
Crew("Crew"),
|
Crew("Crew"),
|
||||||
@@ -76,6 +76,7 @@ public enum AbilityKey {
|
|||||||
LifeGained("LifeGained"),
|
LifeGained("LifeGained"),
|
||||||
Mana("Mana"),
|
Mana("Mana"),
|
||||||
MergedCards("MergedCards"),
|
MergedCards("MergedCards"),
|
||||||
|
Mode("Mode"),
|
||||||
MonstrosityAmount("MonstrosityAmount"),
|
MonstrosityAmount("MonstrosityAmount"),
|
||||||
NewCard("NewCard"),
|
NewCard("NewCard"),
|
||||||
NewCounterAmount("NewCounterAmount"),
|
NewCounterAmount("NewCounterAmount"),
|
||||||
@@ -119,6 +120,7 @@ public enum AbilityKey {
|
|||||||
Token("Token"),
|
Token("Token"),
|
||||||
TokenNum("TokenNum"),
|
TokenNum("TokenNum"),
|
||||||
Transformer("Transformer"),
|
Transformer("Transformer"),
|
||||||
|
TriggeredParams("TriggeredParams"),
|
||||||
Vehicle("Vehicle"),
|
Vehicle("Vehicle"),
|
||||||
Won("Won");
|
Won("Won");
|
||||||
|
|
||||||
|
|||||||
@@ -550,15 +550,21 @@ public class AbilityUtils {
|
|||||||
players.remove(game.getPhaseHandler().getPlayerTurn());
|
players.remove(game.getPhaseHandler().getPlayerTurn());
|
||||||
val = CardFactoryUtil.playerXCount(players, calcX[1], card);
|
val = CardFactoryUtil.playerXCount(players, calcX[1], card);
|
||||||
}
|
}
|
||||||
else if (hType.startsWith("PropertyYou") && ability instanceof SpellAbility) {
|
else if (hType.startsWith("PropertyYou")) {
|
||||||
|
if (ability instanceof SpellAbility) {
|
||||||
// Hollow One
|
// Hollow One
|
||||||
players.add(((SpellAbility) ability).getActivatingPlayer());
|
players.add(((SpellAbility) ability).getActivatingPlayer());
|
||||||
|
} else {
|
||||||
|
players.add(player);
|
||||||
|
}
|
||||||
val = CardFactoryUtil.playerXCount(players, calcX[1], card);
|
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];
|
String defined = hType.split("Property")[1];
|
||||||
for (Player p : game.getPlayersInTurnOrder()) {
|
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);
|
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();
|
final ApiType api = sa.getApi();
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
sa.resolve();
|
sa.resolve();
|
||||||
@@ -1488,17 +1502,45 @@ public class AbilityUtils {
|
|||||||
else if (unlessCost.equals("ChosenNumber")) {
|
else if (unlessCost.equals("ChosenNumber")) {
|
||||||
cost = new Cost(new ManaCost(new ManaCostParser(String.valueOf(source.getChosenNumber()))), true);
|
cost = new Cost(new ManaCost(new ManaCostParser(String.valueOf(source.getChosenNumber()))), true);
|
||||||
}
|
}
|
||||||
else if (unlessCost.equals("RememberedCostMinus2")) {
|
else if (unlessCost.startsWith("DefinedCost")) {
|
||||||
Card rememberedCard = (Card) source.getFirstRemembered();
|
CardCollection definedCards = AbilityUtils.getDefinedCards(sa.getHostCard(), unlessCost.split("_")[1], sa);
|
||||||
if (rememberedCard == null) {
|
if (definedCards.isEmpty()) {
|
||||||
sa.resolve();
|
sa.resolve();
|
||||||
resolveSubAbilities(sa, game);
|
resolveSubAbilities(sa, game);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ManaCostBeingPaid newCost = new ManaCostBeingPaid(rememberedCard.getManaCost());
|
Card card = definedCards.getFirst();
|
||||||
newCost.decreaseGenericMana(2);
|
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);
|
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))) {
|
else if (!StringUtils.isBlank(sa.getSVar(unlessCost)) || !StringUtils.isBlank(source.getSVar(unlessCost))) {
|
||||||
// check for X costs (stored in SVars
|
// check for X costs (stored in SVars
|
||||||
int xCost = calculateAmount(source, TextUtil.fastReplace(sa.getParam("UnlessCost"),
|
int xCost = calculateAmount(source, TextUtil.fastReplace(sa.getParam("UnlessCost"),
|
||||||
@@ -1826,6 +1868,10 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
return CardFactoryUtil.doXMath(v, expr, c);
|
return CardFactoryUtil.doXMath(v, expr, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sq[0].equals("ResolvedThisTurn")) {
|
||||||
|
return CardFactoryUtil.doXMath(sa.getResolvedThisTurn(), expr, c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (l[0].startsWith("CountersAddedThisTurn")) {
|
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);
|
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);
|
return CardFactoryUtil.xCount(c, s2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final void applyManaColorConversion(ManaConversionMatrix matrix, final Map<String, String> params) {
|
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)
|
for (String pair : conversion.split(" ")) {
|
||||||
boolean additive = "Additive".equals(conversionType);
|
// 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) {
|
if (sides[0].equals("AnyColor") || sides[0].equals("AnyType")) {
|
||||||
// Use the strings from MagicColor, since that's how the Script will be coming in as
|
for (byte c : (sides[0].equals("AnyColor") ? MagicColor.WUBRG : MagicColor.WUBRGC)) {
|
||||||
String key = StringUtils.capitalize(c) + "Conversion";
|
matrix.adjustColorReplacement(c, ManaAtom.fromConversion(sides[1]), additive);
|
||||||
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 {
|
} else {
|
||||||
for (final String convertColor : convertTo.split(",")) {
|
matrix.adjustColorReplacement(ManaAtom.fromConversion(sides[0]), ManaAtom.fromConversion(sides[1]), additive);
|
||||||
convertByte |= ManaAtom.fromName(convertColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// AdjustColorReplacement has two different matrices handling final mana conversion under the covers
|
|
||||||
matrix.adjustColorReplacement(ManaAtom.fromName(c), convertByte, 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) {
|
public static SpellAbility addSpliceEffects(final SpellAbility sa) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final Player player = sa.getActivatingPlayer();
|
final Player player = sa.getActivatingPlayer();
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public enum ApiType {
|
|||||||
Block (BlockEffect.class),
|
Block (BlockEffect.class),
|
||||||
Bond (BondEffect.class),
|
Bond (BondEffect.class),
|
||||||
Branch (BranchEffect.class),
|
Branch (BranchEffect.class),
|
||||||
|
Camouflage (CamouflageEffect.class),
|
||||||
ChangeCombatants (ChangeCombatantsEffect.class),
|
ChangeCombatants (ChangeCombatantsEffect.class),
|
||||||
ChangeTargets (ChangeTargetsEffect.class),
|
ChangeTargets (ChangeTargetsEffect.class),
|
||||||
ChangeText (ChangeTextEffect.class),
|
ChangeText (ChangeTextEffect.class),
|
||||||
@@ -92,6 +93,7 @@ public enum ApiType {
|
|||||||
Haunt (HauntEffect.class),
|
Haunt (HauntEffect.class),
|
||||||
Investigate (InvestigateEffect.class),
|
Investigate (InvestigateEffect.class),
|
||||||
ImmediateTrigger (ImmediateTriggerEffect.class),
|
ImmediateTrigger (ImmediateTriggerEffect.class),
|
||||||
|
Learn (LearnEffect.class),
|
||||||
LookAt (LookAtEffect.class),
|
LookAt (LookAtEffect.class),
|
||||||
LoseLife (LifeLoseEffect.class),
|
LoseLife (LifeLoseEffect.class),
|
||||||
LosesGame (GameLossEffect.class),
|
LosesGame (GameLossEffect.class),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.game.ability;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -19,6 +20,7 @@ import forge.game.GameEntity;
|
|||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
|
import forge.game.card.CardCollectionView;
|
||||||
import forge.game.card.CardFactoryUtil;
|
import forge.game.card.CardFactoryUtil;
|
||||||
import forge.game.card.CardZoneTable;
|
import forge.game.card.CardZoneTable;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
@@ -634,10 +636,19 @@ public abstract class SpellAbilityEffect {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
CardZoneTable untilTable = new CardZoneTable();
|
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 (Table.Cell<ZoneType, ZoneType, CardCollection> cell : triggerList.cellSet()) {
|
||||||
for (Card c : cell.getValue()) {
|
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?
|
// 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)) {
|
if (newCard == null || !newCard.equalsWithTimestamp(c)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -646,9 +657,44 @@ public abstract class SpellAbilityEffect {
|
|||||||
untilTable.put(cell.getColumnKey(), cell.getRowKey(), movedCard);
|
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);
|
makeTokens(prototype, activator, sa, 1, true, false, triggerList, combatChanged);
|
||||||
|
|
||||||
if (!useZoneTable) {
|
if (!useZoneTable) {
|
||||||
triggerList.triggerChangesZoneAll(game);
|
triggerList.triggerChangesZoneAll(game, sa);
|
||||||
triggerList.clear();
|
triggerList.clear();
|
||||||
}
|
}
|
||||||
game.fireEvent(new GameEventTokenCreated());
|
game.fireEvent(new GameEventTokenCreated());
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import forge.game.card.Card;
|
|||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
import forge.game.event.GameEventCardStatsChanged;
|
import forge.game.event.GameEventCardStatsChanged;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.util.Lang;
|
||||||
|
import forge.util.TextUtil;
|
||||||
|
|
||||||
|
|
||||||
public class AnimateEffect extends AnimateEffectBase {
|
public class AnimateEffect extends AnimateEffectBase {
|
||||||
@@ -139,6 +141,17 @@ public class AnimateEffect extends AnimateEffectBase {
|
|||||||
|
|
||||||
List<Card> tgts = getCardsfromTargets(sa);
|
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) {
|
for (final Card c : tgts) {
|
||||||
doAnimate(c, sa, power, toughness, types, removeTypes, finalDesc,
|
doAnimate(c, sa, power, toughness, types, removeTypes, finalDesc,
|
||||||
keywords, removeKeywords, hiddenKeywords,
|
keywords, removeKeywords, hiddenKeywords,
|
||||||
@@ -229,9 +242,7 @@ public class AnimateEffect extends AnimateEffectBase {
|
|||||||
|
|
||||||
final List<Card> tgts = getCardsfromTargets(sa);
|
final List<Card> tgts = getCardsfromTargets(sa);
|
||||||
|
|
||||||
for (final Card c : tgts) {
|
sb.append(Lang.joinHomogenous(tgts)).append(" ");
|
||||||
sb.append(c).append(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
// if power is -1, we'll assume it's not just setting toughness
|
// if power is -1, we'll assume it's not just setting toughness
|
||||||
if (power != null && toughness != null) {
|
if (power != null && toughness != null) {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class AttachEffect extends SpellAbilityEffect {
|
|||||||
if (newZone != previousZone) {
|
if (newZone != previousZone) {
|
||||||
table.put(previousZone, newZone, c);
|
table.put(previousZone, newZone, c);
|
||||||
}
|
}
|
||||||
table.triggerChangesZoneAll(game);
|
table.triggerChangesZoneAll(game, sa);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
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