Compare commits
936 Commits
playtest-2
...
release-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b733b10f27 | ||
|
|
ab3f3000fd | ||
|
|
b278a63e11 | ||
|
|
7ba140e471 | ||
|
|
df476dd96f | ||
|
|
f813f2bd46 | ||
|
|
c9c24490e6 | ||
|
|
11d84041d9 | ||
|
|
7ec9bdf0d0 | ||
|
|
ef59b07a2d | ||
|
|
74e3ded764 | ||
|
|
a46fab4ece | ||
|
|
81a7c3446d | ||
|
|
a3404fac66 | ||
|
|
bb2276498b | ||
|
|
7367b546e8 | ||
|
|
becc180956 | ||
|
|
656ce35330 | ||
|
|
2575b41492 | ||
|
|
acaf535b67 | ||
|
|
503a434a4b | ||
|
|
e27c093536 | ||
|
|
fed7daa03d | ||
|
|
3906c6efab | ||
|
|
ae018235d0 | ||
|
|
a8f6256c04 | ||
|
|
de6a9dc8da | ||
|
|
f14e3669e3 | ||
|
|
21eb585a28 | ||
|
|
f8dddf0502 | ||
|
|
d3482c0f18 | ||
|
|
d1c9b8d30f | ||
|
|
d2a6b7370e | ||
|
|
20b55ab5db | ||
|
|
c7ffc4d24f | ||
|
|
2ce83bb51f | ||
|
|
ea9f967acd | ||
|
|
4a6927b513 | ||
|
|
cab611b8dd | ||
|
|
47bfa8bce8 | ||
|
|
40c452b07e | ||
|
|
f1b238d7bf | ||
|
|
0028a26193 | ||
|
|
bcdbd02f1c | ||
|
|
805fc5cade | ||
|
|
90ddbbc934 | ||
|
|
5f9126f274 | ||
|
|
a1011efd98 | ||
|
|
135ee781d4 | ||
|
|
56ef43145b | ||
|
|
ff1bfe162b | ||
|
|
c642162d55 | ||
|
|
4bec1d1950 | ||
|
|
e769f51169 | ||
|
|
ee19990749 | ||
|
|
0eefb0bc8c | ||
|
|
dd10b52955 | ||
|
|
9a8b5da727 | ||
|
|
53fe004c01 | ||
|
|
eea0ca1a4e | ||
|
|
ccbc78ea98 | ||
|
|
befcbe3677 | ||
|
|
1f48359899 | ||
|
|
c4aed8af3f | ||
|
|
2d30913f06 | ||
|
|
e3c96cf283 | ||
|
|
325f414d45 | ||
|
|
4371fb058b | ||
|
|
1d3461f5da | ||
|
|
271f2ce539 | ||
|
|
8c29a44795 | ||
|
|
226b96f9b0 | ||
|
|
fa75891f62 | ||
|
|
9c4dd4a08a | ||
|
|
a75de2dd8b | ||
|
|
5249841ab7 | ||
|
|
01204eb414 | ||
|
|
58e86d1499 | ||
|
|
3b3c6a6647 | ||
|
|
31c6e3826e | ||
|
|
14f5c9d228 | ||
|
|
97d165ed94 | ||
|
|
d6c9bedd5a | ||
|
|
00bc08e9a8 | ||
|
|
924cf4a885 | ||
|
|
1f7eef8ffc | ||
|
|
611d12ac78 | ||
|
|
ef9f26a60d | ||
|
|
aeaffc0a8e | ||
|
|
e3084e230e | ||
|
|
4c01c772f8 | ||
|
|
ed94f7680a | ||
|
|
408d66cdaf | ||
|
|
c4a0f2f169 | ||
|
|
26b28d26da | ||
|
|
8ded6dafd4 | ||
|
|
57a94ad667 | ||
|
|
53933a4d8f | ||
|
|
6606d7dd93 | ||
|
|
7a256dcafa | ||
|
|
7899c52b6d | ||
|
|
919c670502 | ||
|
|
aac3174efc | ||
|
|
a8d3d5c79a | ||
|
|
7c852d90fb | ||
|
|
269ce9c406 | ||
|
|
53d98ec255 | ||
|
|
7a7cd21578 | ||
|
|
2b363e4e1c | ||
|
|
b1560ae69c | ||
|
|
f3ebe07540 | ||
|
|
6cac587753 | ||
|
|
09e6cc4add | ||
|
|
5a2f91be1c | ||
|
|
25071b813e | ||
|
|
d61bd675c4 | ||
|
|
582e2774ac | ||
|
|
5a52ce5330 | ||
|
|
31c9b4fc80 | ||
|
|
4953ce314b | ||
|
|
86519bfda5 | ||
|
|
2c9a36b9a3 | ||
|
|
d3847d49ed | ||
|
|
57f40a0b20 | ||
|
|
fa05f4e4b0 | ||
|
|
754f41ecd1 | ||
|
|
4b6e1c2198 | ||
|
|
e6db2c98d0 | ||
|
|
942f1e2d9e | ||
|
|
da5c94858f | ||
|
|
77ffc20a5f | ||
|
|
d75fed3a00 | ||
|
|
2aba054fe8 | ||
|
|
3b1f4ba07f | ||
|
|
d35768e0f9 | ||
|
|
07a10069db | ||
|
|
20fe59e844 | ||
|
|
6ad5b9ebc4 | ||
|
|
dd0b08d54a | ||
|
|
1cc1f93fb0 | ||
|
|
72f1f06ebc | ||
|
|
151cea96e9 | ||
|
|
888915b53b | ||
|
|
de7a84e8ed | ||
|
|
1dd5b113c7 | ||
|
|
e9ad38667e | ||
|
|
05e8026713 | ||
|
|
5b75649888 | ||
|
|
c22392eb1e | ||
|
|
7c2a51df38 | ||
|
|
05cb9b1fbf | ||
|
|
6bba35c330 | ||
|
|
90d9ee1f5c | ||
|
|
385f01247b | ||
|
|
996029ee38 | ||
|
|
87929b3d91 | ||
|
|
e7e50cc101 | ||
|
|
d6c0926856 | ||
|
|
99ec119ffd | ||
|
|
6b2920cc91 | ||
|
|
05f933f007 | ||
|
|
80aeb5ada6 | ||
|
|
83ea65d4ff | ||
|
|
46b3b01b89 | ||
|
|
973f679939 | ||
|
|
6de1b7b915 | ||
|
|
e7ce739fec | ||
|
|
bf14a4ce80 | ||
|
|
8840a690c6 | ||
|
|
77e85e7c58 | ||
|
|
6943cf5ad5 | ||
|
|
17996dfdfc | ||
|
|
7f32776701 | ||
|
|
fce109a46d | ||
|
|
748292324a | ||
|
|
efe0de2ecb | ||
|
|
cac9940736 | ||
|
|
559b143265 | ||
|
|
a66305e282 | ||
|
|
b3aa61ee8e | ||
|
|
80436a3195 | ||
|
|
5127a6813d | ||
|
|
84eb3c54ef | ||
|
|
ea3c7a3c34 | ||
|
|
718cf37146 | ||
|
|
10f645bf77 | ||
|
|
eda9966d27 | ||
|
|
672172d1f1 | ||
|
|
a366e37014 | ||
|
|
d66e0bb22e | ||
|
|
13581c030d | ||
|
|
13a8b6bda2 | ||
|
|
3aaaa95618 | ||
|
|
e3929d7ded | ||
|
|
2c62f747d9 | ||
|
|
e08818cca1 | ||
|
|
b4670345dd | ||
|
|
0320dcdef9 | ||
|
|
8b12cce250 | ||
|
|
6bb7ab0f97 | ||
|
|
b32346d65f | ||
|
|
8f9c212921 | ||
|
|
df39d4fcc4 | ||
|
|
4daa5193b6 | ||
|
|
13596c1474 | ||
|
|
bb71b59e18 | ||
|
|
f2797c711c | ||
|
|
6b6b1e56e6 | ||
|
|
7b75a78e38 | ||
|
|
2cea4b26e8 | ||
|
|
48f4a98c6a | ||
|
|
9ddc9073c2 | ||
|
|
774f2e0852 | ||
|
|
f5a963ac47 | ||
|
|
38f1f1e5c2 | ||
|
|
4cdbf74256 | ||
|
|
bd0738c5c4 | ||
|
|
72c82cb080 | ||
|
|
b9dd59cd63 | ||
|
|
79019b06ca | ||
|
|
90b25be1b6 | ||
|
|
e4faa6b0f0 | ||
|
|
1a3dfdc67f | ||
|
|
8d2156fb30 | ||
|
|
5e032edd28 | ||
|
|
63d597e4ad | ||
|
|
4135045ca4 | ||
|
|
538623c835 | ||
|
|
3674583053 | ||
|
|
daa8c74c37 | ||
|
|
8aeec24c9b | ||
|
|
54c4a05062 | ||
|
|
466de89e17 | ||
|
|
6eaf51d450 | ||
|
|
7c8dc5d5f4 | ||
|
|
4f34d3edb3 | ||
|
|
0efdbc762d | ||
|
|
5e42c03afc | ||
|
|
58726160a9 | ||
|
|
8cd9215756 | ||
|
|
0e33640b14 | ||
|
|
a1c8cbab0b | ||
|
|
50b484df56 | ||
|
|
99facd2797 | ||
|
|
f79e1cacf0 | ||
|
|
82069db724 | ||
|
|
5a7dc385a3 | ||
|
|
49e7a33db0 | ||
|
|
90b26681eb | ||
|
|
14fc0254c6 | ||
|
|
214aa64ce3 | ||
|
|
a7bb217887 | ||
|
|
10cb8090ec | ||
|
|
1fdecb4e9d | ||
|
|
575596c9ad | ||
|
|
e11c8436bd | ||
|
|
c23efea402 | ||
|
|
8d3cec5bea | ||
|
|
87389d3051 | ||
|
|
71e3ca4493 | ||
|
|
2adee1e374 | ||
|
|
094ccf76b0 | ||
|
|
bb116034c7 | ||
|
|
da53d5b776 | ||
|
|
75fe0e524f | ||
|
|
0ded0355c1 | ||
|
|
4da14cee0a | ||
|
|
90c8e112e2 | ||
|
|
d49bf81d95 | ||
|
|
abbe16b6ec | ||
|
|
274bf06eae | ||
|
|
dcf7b6b3f6 | ||
|
|
4173e40a9d | ||
|
|
1abd272862 | ||
|
|
946bf7b9fa | ||
|
|
0d64fa549b | ||
|
|
8a7020b4ef | ||
|
|
1bc19e788c | ||
|
|
c17110dac5 | ||
|
|
d07b34e67e | ||
|
|
accecee018 | ||
|
|
1861174d38 | ||
|
|
8d5ed65feb | ||
|
|
a375f0e58a | ||
|
|
b2b639434c | ||
|
|
5eadd26f66 | ||
|
|
c0cbca26ea | ||
|
|
8fb65fd9bf | ||
|
|
9072d645fd | ||
|
|
597b8b1caa | ||
|
|
2c0d512727 | ||
|
|
87c5cc96ad | ||
|
|
fc844cfa6d | ||
|
|
1ab1c30e39 | ||
|
|
f2a1a497c7 | ||
|
|
1ddbe50b3f | ||
|
|
dd7b8b24af | ||
|
|
6dcb701d1d | ||
|
|
f67b7ad837 | ||
|
|
a4a409f39b | ||
|
|
904a5f60d1 | ||
|
|
ca8341d432 | ||
|
|
815bbc6ee8 | ||
|
|
ca8870a5cf | ||
|
|
e5da58e2b4 | ||
|
|
dd18829def | ||
|
|
41814a881d | ||
|
|
d708f46d50 | ||
|
|
11f57b2b26 | ||
|
|
60df247416 | ||
|
|
6f32196f89 | ||
|
|
d647aab7fe | ||
|
|
be88c33399 | ||
|
|
7372da150a | ||
|
|
3ec3eac160 | ||
|
|
0990caefd7 | ||
|
|
fc1786e243 | ||
|
|
7e61199458 | ||
|
|
0672553a07 | ||
|
|
8d1f72c104 | ||
|
|
6337067032 | ||
|
|
7395a3ed2d | ||
|
|
e4e1878a4b | ||
|
|
cc8908d7eb | ||
|
|
0dac4520ad | ||
|
|
cc2e369475 | ||
|
|
92189e4b50 | ||
|
|
2e8c85ff0b | ||
|
|
798aff1140 | ||
|
|
8596ce00cc | ||
|
|
b5613acad8 | ||
|
|
7e992d44c8 | ||
|
|
7a7393b9f0 | ||
|
|
f13e6fb76d | ||
|
|
3e83346915 | ||
|
|
761d3583c4 | ||
|
|
1a4b773fda | ||
|
|
930f8ab207 | ||
|
|
2e438f1da9 | ||
|
|
4627387ae3 | ||
|
|
fd69bce609 | ||
|
|
391d9030cb | ||
|
|
1ad9a4b65d | ||
|
|
c6cc2405d3 | ||
|
|
0e39cc7829 | ||
|
|
8d7e5f4663 | ||
|
|
e320bbfc87 | ||
|
|
2e104046d4 | ||
|
|
9cf38c1784 | ||
|
|
1aca6da1ea | ||
|
|
ce4d263b52 | ||
|
|
53c02eb2b9 | ||
|
|
82a2148300 | ||
|
|
b72a58b917 | ||
|
|
7f7bce50dc | ||
|
|
290e214638 | ||
|
|
ad4d6eaec9 | ||
|
|
cd9bf53e1a | ||
|
|
ac8b312140 | ||
|
|
3e849568ff | ||
|
|
5d6961619d | ||
|
|
94180f6a0a | ||
|
|
7803686aec | ||
|
|
b8e60ca8ec | ||
|
|
b985edbc29 | ||
|
|
ffdb3f86d7 | ||
|
|
c9b2a34561 | ||
|
|
e5dc0309f1 | ||
|
|
57a3ad8ae2 | ||
|
|
ad3722e19f | ||
|
|
a12d127fd6 | ||
|
|
e80ebfae35 | ||
|
|
4669d6b378 | ||
|
|
9f093da61e | ||
|
|
c0d31688c4 | ||
|
|
86698e7822 | ||
|
|
3fc5859f08 | ||
|
|
f9bb663f6b | ||
|
|
5e8121bd0b | ||
|
|
9cd6df2929 | ||
|
|
b16cbeacb6 | ||
|
|
4d46464bc6 | ||
|
|
7746dc55f0 | ||
|
|
5d9e8b56c5 | ||
|
|
d1c06365a8 | ||
|
|
b56ddc1a08 | ||
|
|
ae8c8e781f | ||
|
|
1344b1f2e3 | ||
|
|
36df25dcb4 | ||
|
|
84246d287d | ||
|
|
6d0fbfa21f | ||
|
|
034c6824de | ||
|
|
c8afa4a2a8 | ||
|
|
1a77f7320b | ||
|
|
183ece1360 | ||
|
|
035cc3da99 | ||
|
|
c01c39954a | ||
|
|
c48eb572e3 | ||
|
|
b03ab1212f | ||
|
|
8a9b5e7e01 | ||
|
|
9b90e4f25a | ||
|
|
8a4401bdcb | ||
|
|
d52e90cf23 | ||
|
|
14ef0a7740 | ||
|
|
c1f79b348a | ||
|
|
62166a50d9 | ||
|
|
283b330403 | ||
|
|
2cf6b74295 | ||
|
|
1a9f707d18 | ||
|
|
06a1c88e86 | ||
|
|
f358b566b1 | ||
|
|
15fc27d142 | ||
|
|
ad20597d74 | ||
|
|
6d409a7c97 | ||
|
|
36d5ae5421 | ||
|
|
db9744ea7f | ||
|
|
5e62fe86fc | ||
|
|
6cfa27c33b | ||
|
|
a405969199 | ||
|
|
9a6f3b4c05 | ||
|
|
7be059a79b | ||
|
|
29b55de042 | ||
|
|
235fb19aa8 | ||
|
|
c0f54fa4fc | ||
|
|
cdc216aca0 | ||
|
|
4505053618 | ||
|
|
e0d53126d6 | ||
|
|
12ff1dd14c | ||
|
|
50db3152f6 | ||
|
|
9c4fd0e3d3 | ||
|
|
8d27d22100 | ||
|
|
27f1a7ab27 | ||
|
|
d52e4793fe | ||
|
|
544ac6cb33 | ||
|
|
06fbc1a6cf | ||
|
|
edab10e6a6 | ||
|
|
dd99fc93e4 | ||
|
|
dbe824d4e5 | ||
|
|
6e73d7f5c2 | ||
|
|
960056d300 | ||
|
|
3efac3287e | ||
|
|
c4b4a8c8a5 | ||
|
|
b833f033bf | ||
|
|
ad75e2be89 | ||
|
|
7ee4fbeb0d | ||
|
|
9886f0ca9a | ||
|
|
46cf56d6ff | ||
|
|
e7af295b5e | ||
|
|
9d179d9a1a | ||
|
|
15010f9567 | ||
|
|
a7f4f6c1cf | ||
|
|
3eeb677f14 | ||
|
|
ef69a3de66 | ||
|
|
0aa5e07252 | ||
|
|
07d58337f1 | ||
|
|
b5e3f25418 | ||
|
|
19b02875c7 | ||
|
|
8a74f6ea18 | ||
|
|
a847f3eafa | ||
|
|
a751f074e7 | ||
|
|
75cb5c2166 | ||
|
|
70a86bed7a | ||
|
|
f67f8ed05e | ||
|
|
1ae53220d6 | ||
|
|
c546cb552e | ||
|
|
10f8836d7b | ||
|
|
7ecd4124ce | ||
|
|
54cd77be8e | ||
|
|
43388cb7fc | ||
|
|
4cc5104fde | ||
|
|
d519cabae3 | ||
|
|
9852e29835 | ||
|
|
3a427c3630 | ||
|
|
336656e8f7 | ||
|
|
06ad9666e8 | ||
|
|
728e0c6600 | ||
|
|
657e690bdd | ||
|
|
2d36d0a659 | ||
|
|
c42fd5d2e2 | ||
|
|
10bf97eff6 | ||
|
|
ea9992247d | ||
|
|
b90fecff76 | ||
|
|
a4fc9fea3b | ||
|
|
b7c7eff2a2 | ||
|
|
8c10dc406a | ||
|
|
05c3861426 | ||
|
|
1ef5db8896 | ||
|
|
006a87692a | ||
|
|
e019b70420 | ||
|
|
70ec5b0344 | ||
|
|
5401ace540 | ||
|
|
ab9081c852 | ||
|
|
3a9b35980c | ||
|
|
150439d215 | ||
|
|
b01a534a98 | ||
|
|
758b0b08d0 | ||
|
|
f87ba1d8a4 | ||
|
|
67fa7bdcc9 | ||
|
|
0b03aca104 | ||
|
|
3bf61f1043 | ||
|
|
ac975f4139 | ||
|
|
6d12301f88 | ||
|
|
914950c4a5 | ||
|
|
b417b267dd | ||
|
|
aae497eff1 | ||
|
|
3c9db4c2ac | ||
|
|
8c3793e7ea | ||
|
|
60a7f53491 | ||
|
|
04bfd62f2f | ||
|
|
117b8b3653 | ||
|
|
70cb0d2924 | ||
|
|
c5ea496c45 | ||
|
|
01e955ca37 | ||
|
|
fdb66c769c | ||
|
|
38b3fbbdbe | ||
|
|
3bc5d2d02c | ||
|
|
ac7eda8ca2 | ||
|
|
baf58f53b3 | ||
|
|
8513a83331 | ||
|
|
bf7fecff10 | ||
|
|
6e1f2f636c | ||
|
|
8b3db6f3d6 | ||
|
|
39d0abe982 | ||
|
|
c2026dc254 | ||
|
|
cae6c28754 | ||
|
|
3b99924799 | ||
|
|
b2b548b103 | ||
|
|
c2e3806a77 | ||
|
|
25500a7dda | ||
|
|
8c394a4cb5 | ||
|
|
2e7bd4de4b | ||
|
|
7261322e41 | ||
|
|
cada396733 | ||
|
|
9f9709f058 | ||
|
|
26fc65209d | ||
|
|
f642cead44 | ||
|
|
616d9421d6 | ||
|
|
20e5219cf4 | ||
|
|
9edda21b06 | ||
|
|
6d6822ca15 | ||
|
|
a2269e7ee7 | ||
|
|
e8f443f4a9 | ||
|
|
67f8452178 | ||
|
|
47f6e407d9 | ||
|
|
889e2152a4 | ||
|
|
173aae1f81 | ||
|
|
baed80983b | ||
|
|
b00423dc76 | ||
|
|
376ed15079 | ||
|
|
a6d8d6cd8e | ||
|
|
b3ee8b447e | ||
|
|
5f588561b6 | ||
|
|
83c53e17e0 | ||
|
|
c028488894 | ||
|
|
b066005f7e | ||
|
|
87e33a75c6 | ||
|
|
4961b0943b | ||
|
|
eda1e9c266 | ||
|
|
bc7bf174d8 | ||
|
|
2e06d5790b | ||
|
|
ab8790e8f1 | ||
|
|
7d630e63e7 | ||
|
|
77899191f3 | ||
|
|
6871873e93 | ||
|
|
fc4bd131cd | ||
|
|
d866286f82 | ||
|
|
76dfda164e | ||
|
|
134d47e48c | ||
|
|
a2dbd5e013 | ||
|
|
c27412c83a | ||
|
|
4143aba595 | ||
|
|
39ccac4022 | ||
|
|
7943f4deb6 | ||
|
|
2dda2d7689 | ||
|
|
b8f2a14ea0 | ||
|
|
595809f090 | ||
|
|
477db9cd4a | ||
|
|
67ff292d62 | ||
|
|
ae882b85a9 | ||
|
|
5e92915095 | ||
|
|
95809db03c | ||
|
|
6d5a5121bc | ||
|
|
86992751c7 | ||
|
|
2eba8b6c37 | ||
|
|
e1523e939d | ||
|
|
15a92f443d | ||
|
|
b57c68e392 | ||
|
|
e95fcb6bc0 | ||
|
|
6581fcb6a7 | ||
|
|
6551337bd8 | ||
|
|
137df52fdd | ||
|
|
b79aa7eb6a | ||
|
|
1c8c49dc8e | ||
|
|
f0c808d2fc | ||
|
|
26d9ae88df | ||
|
|
3c7f119bb1 | ||
|
|
fea35923f0 | ||
|
|
9627776318 | ||
|
|
6dcde3af72 | ||
|
|
43717a89b5 | ||
|
|
c7ba359688 | ||
|
|
dc3dbf6d85 | ||
|
|
71664c85ff | ||
|
|
cb41be113a | ||
|
|
4fe7daa85e | ||
|
|
f75afc6ee4 | ||
|
|
67fd71ab92 | ||
|
|
385e70552e | ||
|
|
1e2c67bfca | ||
|
|
56739f87fb | ||
|
|
571eb7614f | ||
|
|
c6c3a8c60d | ||
|
|
ae7cfa56b7 | ||
|
|
b856613194 | ||
|
|
86305879cb | ||
|
|
e5a1a8a706 | ||
|
|
27602a4a97 | ||
|
|
a98e460257 | ||
|
|
0349435650 | ||
|
|
c3fbdca18f | ||
|
|
acb5245a28 | ||
|
|
23561cd76b | ||
|
|
d3ab3d7d78 | ||
|
|
5b870be83f | ||
|
|
6130d5622c | ||
|
|
318c4e3456 | ||
|
|
ab701449e2 | ||
|
|
9a3447d863 | ||
|
|
5280637adf | ||
|
|
3bce55ac44 | ||
|
|
a3f79503ed | ||
|
|
02d462a82c | ||
|
|
3d17328d0d | ||
|
|
0e81abc21b | ||
|
|
803b930405 | ||
|
|
6adf45bcb4 | ||
|
|
01417c88c5 | ||
|
|
60bbbe0d93 | ||
|
|
888dfd3654 | ||
|
|
12de56ff62 | ||
|
|
b7cee41c54 | ||
|
|
10aac03f75 | ||
|
|
0eb0041f90 | ||
|
|
b38018af9c | ||
|
|
4df5ac0385 | ||
|
|
a7476bc303 | ||
|
|
cc4b3cb361 | ||
|
|
31a965b29a | ||
|
|
7a213338a2 | ||
|
|
fb27a25e52 | ||
|
|
534b09ae4a | ||
|
|
341a9f370c | ||
|
|
607d9b2d5c | ||
|
|
507ce40ad2 | ||
|
|
f58c3aed32 | ||
|
|
55e85bd9ca | ||
|
|
4bf614c5cd | ||
|
|
96b06c75d1 | ||
|
|
d261648ab0 | ||
|
|
7b81b9e806 | ||
|
|
a93aea3e4e | ||
|
|
2cfacc2c7d | ||
|
|
6d6b21a0eb | ||
|
|
7a78c37851 | ||
|
|
b8a9f41892 | ||
|
|
227567dfe1 | ||
|
|
d5ff5c672b | ||
|
|
393f6eca3a | ||
|
|
d193ef856e | ||
|
|
44d3691fa1 | ||
|
|
27d0465891 | ||
|
|
52a9fcef3c | ||
|
|
7386816f52 | ||
|
|
9c0075b233 | ||
|
|
e4c5700baf | ||
|
|
e1b7df8b6a | ||
|
|
c999b2d778 | ||
|
|
bfb6c671fb | ||
|
|
7c6ec577dc | ||
|
|
626b40f31b | ||
|
|
5af12440ba | ||
|
|
4614f6febe | ||
|
|
1354ffc32e | ||
|
|
3723939c99 | ||
|
|
e099739e13 | ||
|
|
21a48cc41d | ||
|
|
4740266308 | ||
|
|
78139413d7 | ||
|
|
f0578a75f4 | ||
|
|
7a0e55a02a | ||
|
|
f132bac80d | ||
|
|
109ea4fe5b | ||
|
|
f33feafd0e | ||
|
|
9195356e3a | ||
|
|
eff91108f4 | ||
|
|
e40c0516e6 | ||
|
|
c580a94ab7 | ||
|
|
6a545bb942 | ||
|
|
672bd2d9fe | ||
|
|
3ab4a584ab | ||
|
|
150d02ac0d | ||
|
|
6d26f60904 | ||
|
|
b42276953f | ||
|
|
7c290b9f76 | ||
|
|
51fe1d6629 | ||
|
|
8f558d2b47 | ||
|
|
3f5fadf2e9 | ||
|
|
07c16cee1d | ||
|
|
24130dfcdc | ||
|
|
15a2341a91 | ||
|
|
86f61298e6 | ||
|
|
3cd7ec3878 | ||
|
|
8b13d3e4c7 | ||
|
|
327d451abc | ||
|
|
2dac16ee02 | ||
|
|
d4b92a19d7 | ||
|
|
e0357596f5 | ||
|
|
1ef27d18c1 | ||
|
|
1d2d8ed107 | ||
|
|
1bf01bc214 | ||
|
|
0015deca47 | ||
|
|
ddfdc6e90f | ||
|
|
5db2ad54f2 | ||
|
|
b8a5750529 | ||
|
|
98d5b8c7cc | ||
|
|
3f34154a1e | ||
|
|
3119f831b3 | ||
|
|
8a07b762a2 | ||
|
|
551ab2fc59 | ||
|
|
99957e57b9 | ||
|
|
be2c59bc6e | ||
|
|
57f9a49b66 | ||
|
|
54bd0eb99d | ||
|
|
485faac294 | ||
|
|
d531d6f3ef | ||
|
|
3a9fdb82f5 | ||
|
|
5024ae1156 | ||
|
|
0135dd9ed3 | ||
|
|
a6e9b86bbe | ||
|
|
237c4444b5 | ||
|
|
a68467292e | ||
|
|
978c69d0c3 | ||
|
|
3eabc59921 | ||
|
|
2b3d99fac2 | ||
|
|
2bdefe0e9e | ||
|
|
de81fc2aca | ||
|
|
b514e0a6e7 | ||
|
|
715dfa4541 | ||
|
|
ac57a37224 | ||
|
|
afd620b092 | ||
|
|
552bceb07c | ||
|
|
fe58ed1283 | ||
|
|
361e2d463c | ||
|
|
259c8d2c98 | ||
|
|
e12c1dc9aa | ||
|
|
7fb49e383d | ||
|
|
1df3e28253 | ||
|
|
bacec2689d | ||
|
|
bf397591f9 | ||
|
|
bd1a936c7a | ||
|
|
0871d6e321 | ||
|
|
265d296db6 | ||
|
|
5ec136b57c | ||
|
|
f1e8f9c9d0 | ||
|
|
f03841c4e4 | ||
|
|
0ae58ff0ea | ||
|
|
390c1899ca | ||
|
|
89aa6d1e4e | ||
|
|
24638b02a9 | ||
|
|
4b0ab6ab37 | ||
|
|
1f02d9f141 | ||
|
|
0103c38c13 | ||
|
|
3cc76f91b4 | ||
|
|
10dc248f07 | ||
|
|
d1f89c6217 | ||
|
|
76ba4fc32d | ||
|
|
37f90fff44 | ||
|
|
9fa6da3bc7 | ||
|
|
aa8cf237ab | ||
|
|
c131728aa4 | ||
|
|
2abd137494 | ||
|
|
90815ace59 | ||
|
|
53d916d7f1 | ||
|
|
2b4035979b | ||
|
|
38cdc93010 | ||
|
|
346dad3898 | ||
|
|
42256bc262 | ||
|
|
70babb4067 | ||
|
|
3603e6373d | ||
|
|
bd1760682f | ||
|
|
52d0490f95 | ||
|
|
b3b0aa75ae | ||
|
|
e42d177920 | ||
|
|
c2156af7b0 | ||
|
|
86394eb56c | ||
|
|
2d7790f5e4 | ||
|
|
3df43529a6 | ||
|
|
a1c9b27057 | ||
|
|
089dd233a5 | ||
|
|
86a7a0bd6c | ||
|
|
7ebca36a9c | ||
|
|
d5aed5a88a | ||
|
|
dac1f270ce | ||
|
|
839be24053 | ||
|
|
91c4179f05 | ||
|
|
a4b427bfac | ||
|
|
250f5bec18 | ||
|
|
336e2a10e0 | ||
|
|
b5e9b7635e | ||
|
|
0df7fa1596 | ||
|
|
c10487d635 | ||
|
|
bc9b3bef74 | ||
|
|
e57462e7ca | ||
|
|
78bf27709f | ||
|
|
fc84cd9204 | ||
|
|
e361f7b246 | ||
|
|
b0497b7505 | ||
|
|
a894e31fa5 | ||
|
|
dd062adec2 | ||
|
|
d187575a2c | ||
|
|
85096c4ba2 | ||
|
|
e13fd693c3 | ||
|
|
cc35512472 | ||
|
|
4e548291ce | ||
|
|
3ba86f329f | ||
|
|
5b34af0f12 | ||
|
|
0a9eb1ff83 | ||
|
|
942dd0e5f7 | ||
|
|
400102f3d3 | ||
|
|
9ccdeb3d36 | ||
|
|
7e0f0dd2d2 | ||
|
|
d920cbb7f6 | ||
|
|
417677e6f4 | ||
|
|
33f3038316 | ||
|
|
429dbe3e0c | ||
|
|
471fc44751 | ||
|
|
1b8e346307 | ||
|
|
a456583a08 | ||
|
|
a63c17baab | ||
|
|
9c4faddc0f | ||
|
|
6828c4c1e9 | ||
|
|
80131e7ec0 | ||
|
|
ac381a6f58 | ||
|
|
521b516bf9 | ||
|
|
6a825f8e60 | ||
|
|
f0a243ca10 | ||
|
|
e5457a3390 | ||
|
|
47e21f8bef | ||
|
|
331b854e4e | ||
|
|
274bc9cbba | ||
|
|
827f8d95b4 | ||
|
|
2946dd35d5 | ||
|
|
74d884787d | ||
|
|
5516e16fb8 | ||
|
|
cadf4eb322 | ||
|
|
269249e86e | ||
|
|
30f87d2308 | ||
|
|
8b7e72b95e | ||
|
|
1e64048956 | ||
|
|
8512e696f5 | ||
|
|
6a03a9ec5f | ||
|
|
5e04c99d57 | ||
|
|
82f15491c0 | ||
|
|
101843fbb7 | ||
|
|
9e534f3804 | ||
|
|
ca3cfc0184 | ||
|
|
d62fb901e2 | ||
|
|
73a2b59c2c | ||
|
|
2c7a56625c | ||
|
|
e2572b214f | ||
|
|
d22cd3a74f | ||
|
|
0c8fcedfdf | ||
|
|
99009c37ce | ||
|
|
d9f5771778 | ||
|
|
d35b5070fb | ||
|
|
02f41f9afc | ||
|
|
c797aa1d5e | ||
|
|
b2f7f67756 | ||
|
|
09014ab6d5 | ||
|
|
8f8747d65e | ||
|
|
be19e137e2 | ||
|
|
3155291064 | ||
|
|
a5b22e6a36 | ||
|
|
9c251e8b6a | ||
|
|
6056568182 | ||
|
|
7b7c1da18d | ||
|
|
fb5b4b3547 | ||
|
|
19918d485e | ||
|
|
45c6c6ba10 | ||
|
|
0e436bc686 | ||
|
|
b0dfea0a09 | ||
|
|
2c4e6c4188 | ||
|
|
9f3254dbd1 | ||
|
|
88cdad4189 | ||
|
|
4ba50a4379 | ||
|
|
2b6c104011 | ||
|
|
4b446d100e | ||
|
|
f9ca2114a9 | ||
|
|
afc9c6ef85 | ||
|
|
ac200f6173 | ||
|
|
73a78eadb1 | ||
|
|
c5139fb6c2 | ||
|
|
9faf9aa1b9 | ||
|
|
3c2e9be248 | ||
|
|
5b59f6612f | ||
|
|
3d69363f35 | ||
|
|
b580b4fd33 | ||
|
|
74f86d70f8 | ||
|
|
3959104f9b | ||
|
|
1ff037a257 | ||
|
|
32700df117 | ||
|
|
b4edec215e | ||
|
|
dffa1e45f4 | ||
|
|
df3b6dde34 | ||
|
|
834bbf467e | ||
|
|
4d4f94208e | ||
|
|
416713de0c | ||
|
|
c523ca8efe | ||
|
|
9acea56108 | ||
|
|
44a7422375 | ||
|
|
0d0e7eb179 | ||
|
|
ea6c840343 | ||
|
|
d2db0913ac | ||
|
|
3721dae74d | ||
|
|
9050a2447b | ||
|
|
df4c363e9c | ||
|
|
a909a3e692 | ||
|
|
dd26253905 | ||
|
|
69b7ba2d22 | ||
|
|
d2f306e488 | ||
|
|
4a6fefa434 | ||
|
|
16e0ea611e | ||
|
|
05a2e77be2 | ||
|
|
dd2fa36261 | ||
|
|
5fa1dec6d8 | ||
|
|
f86d96794d |
@@ -6,13 +6,101 @@ charset=utf-8
|
||||
[*]
|
||||
end_of_line = LF
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
; 4-column tab indentation
|
||||
; 4-column tab indentation and .NET coding conventions
|
||||
[*.cs]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
dotnet_separate_import_directive_groups = false
|
||||
dotnet_sort_system_directives_first = true
|
||||
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
|
||||
csharp_prefer_braces = when_multiline:suggestion
|
||||
csharp_using_directive_placement = outside_namespace:suggestion
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
|
||||
#### Naming styles ####
|
||||
|
||||
dotnet_naming_style.camel_case.capitalization = camel_case
|
||||
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal
|
||||
|
||||
dotnet_naming_symbols.const_private_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.const_private_field.required_modifiers = const
|
||||
dotnet_naming_symbols.const_private_field.applicable_accessibilities = private
|
||||
|
||||
dotnet_naming_symbols.internal_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.internal_field.applicable_accessibilities = internal
|
||||
|
||||
dotnet_naming_symbols.static_private_or_internal_field.required_modifiers = static
|
||||
dotnet_naming_symbols.static_private_or_internal_field.applicable_accessibilities = internal, private
|
||||
|
||||
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
|
||||
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.severity = none
|
||||
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.symbols = static_private_or_internal_field
|
||||
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.const_private_field_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.const_private_field_should_be_pascal_case.symbols = const_private_field
|
||||
dotnet_naming_rule.const_private_field_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.severity = warning
|
||||
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.symbols = internal_field
|
||||
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.severity = warning
|
||||
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.symbols = private_or_internal_field
|
||||
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.style = camel_case
|
||||
|
||||
# Naming rules
|
||||
|
||||
#require a space before the colon for bases or interfaces in a type declaration
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
#require a space after a keyword in a control flow statement such as a for loop
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
#require a space before the colon for bases or interfaces in a type declaration
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
|
||||
#Formatting - wrapping options
|
||||
|
||||
#leave code block on single line
|
||||
csharp_preserve_single_line_blocks = true
|
||||
#leave statements and member declarations on the same line
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
|
||||
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
|
||||
; 4-column tab indentation
|
||||
[*.yaml]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
indent_size = 4
|
||||
|
||||
9
.github/ISSUE_TEMPLATE/config.yml
vendored
9
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,11 +1,14 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Frequently Asked Questions
|
||||
url: https://github.com/OpenRA/OpenRA/wiki/FAQ#frequently-asked-questions
|
||||
about: Explanations for common problems and questions.
|
||||
- name: OpenRA Forum
|
||||
url: https://forum.openra.net/
|
||||
about: "Please ask questions about modding here."
|
||||
about: Please ask questions about modding here.
|
||||
- name: OpenRA Discord server
|
||||
url: https://discord.openra.net/
|
||||
about: "Join the community Discord server for community discussion and support."
|
||||
about: Join the community Discord server for community discussion and support.
|
||||
- name: OpenRA IRC Channel
|
||||
url: https://webchat.freenode.net/#openra
|
||||
about: "Join our development IRC channel on freenode for discussion of development topics."
|
||||
about: Join our development IRC channel on freenode for discussion of development topics.
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/crash-report.md
vendored
2
.github/ISSUE_TEMPLATE/crash-report.md
vendored
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: Crash report
|
||||
about: Report a game crash.
|
||||
about: Report a game crash. Check the FAQ first https://github.com/OpenRA/OpenRA/wiki/FAQ#common-issues
|
||||
title: My game crashed
|
||||
labels: Crash
|
||||
assignees: ''
|
||||
|
||||
49
.github/workflows/ci.yaml
vendored
Normal file
49
.github/workflows/ci.yaml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches: [ bleed ]
|
||||
|
||||
jobs:
|
||||
linux-mono:
|
||||
name: Linux (mono)
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Check Code
|
||||
run: |
|
||||
mono --version
|
||||
make check
|
||||
mono ~/.nuget/packages/nunit.consolerunner/3.11.1/tools/nunit3-console.exe --noresult bin/OpenRA.Test.dll
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
sudo apt-get install lua5.1
|
||||
make check-scripts
|
||||
make test
|
||||
|
||||
windows:
|
||||
name: Windows (Framework 4.7)
|
||||
runs-on: windows-2019
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Check Code
|
||||
shell: powershell
|
||||
run: |
|
||||
dotnet nuget locals all --clear
|
||||
.\make.ps1 check
|
||||
Invoke-Expression "$home\.nuget\packages\nunit.consolerunner\3.11.1\tools\nunit3-console.exe --noresult bin/OpenRA.Test.dll"
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
chocolatey install lua --version 5.1.5.52
|
||||
$ENV:Path = $ENV:Path + ";C:\Program Files (x86)\Lua\5.1\"
|
||||
.\make.ps1 check-scripts
|
||||
.\make.ps1 test
|
||||
107
.github/workflows/documentation.yml
vendored
Normal file
107
.github/workflows/documentation.yml
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
name: Deploy Documentation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git Tag'
|
||||
required: true
|
||||
default: 'release-xxxxxxxx'
|
||||
|
||||
jobs:
|
||||
wiki:
|
||||
name: Update Wiki
|
||||
if: github.repository == 'openra/openra'
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
make all
|
||||
|
||||
- name: Clone Wiki
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: openra/openra.wiki
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: wiki
|
||||
|
||||
- name: Update Wiki (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings (playtest).md"
|
||||
|
||||
- name: Update Wiki (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings.md"
|
||||
|
||||
- name: Push Wiki
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
cd wiki
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
git add --all
|
||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||
git push origin master
|
||||
|
||||
docs:
|
||||
name: Update docs.openra.net
|
||||
if: github.repository == 'openra/openra'
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
make all
|
||||
|
||||
- name: Clone docs.openra.net
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: openra/docs
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: docs
|
||||
|
||||
- name: Update docs.openra.net (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --docs "${GIT_TAG}" > "docs/api/playtest/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" > "docs/api/playtest/weapons.md"
|
||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/playtest/lua.md"
|
||||
|
||||
- name: Update docs.openra.net (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --docs "${GIT_TAG}" > "docs/api/release/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" > "docs/api/release/weapons.md"
|
||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/release/lua.md"
|
||||
|
||||
- name: Push docs.openra.net
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
cd docs
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
git add --all
|
||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||
git push origin master
|
||||
|
||||
86
.github/workflows/itch.yml
vendored
Normal file
86
.github/workflows/itch.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
name: Deploy itch.io Packages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git Tag'
|
||||
required: true
|
||||
default: 'release-xxxxxxxx'
|
||||
|
||||
jobs:
|
||||
itch:
|
||||
name: Deploy to itch.io
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.repository == 'openra/openra'
|
||||
steps:
|
||||
- name: Download Packages
|
||||
run: |
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64.exe"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64-winportable.zip" -O "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}.dmg"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Dune-2000-x86_64.AppImage"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Red-Alert-x86_64.AppImage"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
|
||||
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${{ github.event.inputs.tag }}/packaging/.itch.toml"
|
||||
zip -u "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" .itch.toml
|
||||
|
||||
- name: Publish Windows Installer
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: win
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}}-x64.exe
|
||||
|
||||
- name: Publish Windows Itch Bundle
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: itch
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip
|
||||
|
||||
- name: Publish macOS Package
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: macos
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}}.dmg
|
||||
|
||||
- name: Publish RA AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-ra
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Red-Alert-x86_64.AppImage
|
||||
|
||||
- name: Publish TD AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-cnc
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Tiberian-Dawn-x86_64.AppImage
|
||||
|
||||
- name: Publish D2k AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-d2k
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Dune-2000-x86_64.AppImage
|
||||
114
.github/workflows/packaging.yml
vendored
Normal file
114
.github/workflows/packaging.yml
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
name: Release Packaging
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'release-*'
|
||||
- 'playtest-*'
|
||||
- 'devtest-*'
|
||||
|
||||
jobs:
|
||||
source:
|
||||
name: Source Tarball
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package Source
|
||||
run: |
|
||||
mkdir -p build/source
|
||||
./packaging/source/buildpackage.sh "${GIT_TAG}" "${PWD}/build/source"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/source/*
|
||||
|
||||
linux:
|
||||
name: Linux AppImages
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package AppImages
|
||||
run: |
|
||||
mkdir -p build/linux
|
||||
./packaging/linux/buildpackage.sh "${GIT_TAG}" "${PWD}/build/linux"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/linux/*
|
||||
|
||||
macos:
|
||||
name: macOS Disk Images
|
||||
runs-on: macos-10.15
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package Disk Images
|
||||
env:
|
||||
MACOS_DEVELOPER_IDENTITY: ${{ secrets.MACOS_DEVELOPER_IDENTITY }}
|
||||
MACOS_DEVELOPER_CERTIFICATE_BASE64: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_BASE64 }}
|
||||
MACOS_DEVELOPER_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_PASSWORD }}
|
||||
MACOS_DEVELOPER_USERNAME: ${{ secrets.MACOS_DEVELOPER_USERNAME }}
|
||||
MACOS_DEVELOPER_PASSWORD: ${{ secrets.MACOS_DEVELOPER_PASSWORD }}
|
||||
run: |
|
||||
mkdir -p build/macos
|
||||
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/macos/*
|
||||
|
||||
windows:
|
||||
name: Windows Installers
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
sudo apt-get update
|
||||
sudo apt-get install nsis
|
||||
|
||||
- name: Package Installers
|
||||
run: |
|
||||
mkdir -p build/windows
|
||||
./packaging/windows/buildpackage.sh "${GIT_TAG}" "${PWD}/build/windows"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/windows/*
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -26,7 +26,7 @@ mods/*/*.pdb
|
||||
/*.exe
|
||||
/*.exe.config
|
||||
thirdparty/download/*
|
||||
*.mmdb.gz
|
||||
IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
|
||||
|
||||
# backup files by various editors
|
||||
*~
|
||||
|
||||
86
.travis.yml
86
.travis.yml
@@ -1,86 +0,0 @@
|
||||
# Travis-CI Build for OpenRA
|
||||
# see travis-ci.org for details
|
||||
|
||||
language: csharp
|
||||
mono: 6.4.0
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- os: linux
|
||||
dist: xenial
|
||||
- os: osx
|
||||
if: tag IS present
|
||||
osx_image: xcode10
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- thirdparty/download
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- lua5.1
|
||||
- dpkg
|
||||
- zsync
|
||||
- markdown
|
||||
|
||||
# Environment variables
|
||||
env:
|
||||
secure: "C0+Hlfa0YGErxUuWV00Tj6p45otC/D3YwYFuLpi2mj1rDFn/4dgh5WRngjvdDBVbXJ3duaZ78jPHWm1jr7vn2jqj9yETsCIK9psWd38ep/FEBM0SDr6MUD89OuXk/YyvxJAE+UXF6bXg7giey09g/CwBigjMW7ynET3wNAWPHPs="
|
||||
|
||||
# Fetch dependencies
|
||||
# Run the build script
|
||||
# Check source code with StyleCop
|
||||
# call OpenRA to check for YAML errors
|
||||
# Run the NUnit tests
|
||||
script:
|
||||
- travis_retry make all-dependencies
|
||||
- make all
|
||||
- test "$TRAVIS_OS_NAME" == "linux" && make check || echo "Skipping check"
|
||||
- test "$TRAVIS_OS_NAME" == "linux" && make check-scripts || echo "Skipping scripts check"
|
||||
- test "$TRAVIS_OS_NAME" == "linux" && make test || echo "Skipping tests"
|
||||
- test "$TRAVIS_OS_NAME" == "linux" && make nunit || echo "Skipping nunit tests"
|
||||
|
||||
# Only watch the development branch and tagged release.
|
||||
branches:
|
||||
only:
|
||||
- /^release-.*$/
|
||||
- /^playtest-.*$/
|
||||
- /^pkgtest-.*$/
|
||||
- /^prep-.*$/
|
||||
- bleed
|
||||
|
||||
# Notify developers when build passed/failed.
|
||||
notifications:
|
||||
irc:
|
||||
template:
|
||||
- "%{repository}#%{build_number} %{commit} %{author}: %{message} %{build_url}"
|
||||
channels:
|
||||
- "irc.freenode.net#openra"
|
||||
use_notice: true
|
||||
skip_join: true
|
||||
|
||||
before_deploy:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
wget https://mirrors.edge.kernel.org/ubuntu/pool/universe/n/nsis/nsis-common_3.04-1_all.deb;
|
||||
wget https://mirrors.edge.kernel.org/ubuntu/pool/universe/n/nsis/nsis_3.04-1_amd64.deb;
|
||||
sudo dpkg -i nsis-common_3.04-1_all.deb;
|
||||
sudo dpkg -i nsis_3.04-1_amd64.deb;
|
||||
cd packaging && ./update-wiki.sh ${TRAVIS_TAG} && cd ..;
|
||||
fi;
|
||||
- export PATH=$PATH:$HOME/usr/bin
|
||||
- DOTVERSION=`echo ${TRAVIS_TAG} | sed "s/-/\\./g"`
|
||||
- cd packaging
|
||||
- mkdir build
|
||||
- ./package-all.sh ${TRAVIS_TAG} ${PWD}/build/
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: "g/LU11f+mjqv+lj0sR1UliHwogXL4ofJUwoG5Dbqlvdf5UTLWytw/OWSCv8RGyuh10miyWeaoqHh1cn2C1IFhUEqN1sSeKKKOWOTvJ2FR5mzi9uH3d/MOBzG5icQ7Qh0fZ1YPz5RaJJhYu6bmfvA/1gD49GoaX2kxQL4J5cEBgg="
|
||||
file_glob: true
|
||||
file: build/*
|
||||
skip_cleanup: true
|
||||
on:
|
||||
all_branches: true
|
||||
tags: true
|
||||
repo: OpenRA/OpenRA
|
||||
7
.vscode/extensions.json
vendored
Normal file
7
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"ms-dotnettools.csharp",
|
||||
"EditorConfig.EditorConfig",
|
||||
"ms-vscode.mono-debug"
|
||||
]
|
||||
}
|
||||
61
.vscode/launch.json
vendored
Normal file
61
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch (TD)",
|
||||
"type": "clr",
|
||||
"linux": {
|
||||
"type": "mono"
|
||||
},
|
||||
"osx": {
|
||||
"type": "mono"
|
||||
},
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
"args": ["Game.Mod=cnc", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (RA)",
|
||||
"type": "clr",
|
||||
"linux": {
|
||||
"type": "mono"
|
||||
},
|
||||
"osx": {
|
||||
"type": "mono"
|
||||
},
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
"args": ["Game.Mod=ra", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (D2k)",
|
||||
"type": "clr",
|
||||
"linux": {
|
||||
"type": "mono"
|
||||
},
|
||||
"osx": {
|
||||
"type": "mono"
|
||||
},
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
"args": ["Game.Mod=d2k", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (TS)",
|
||||
"type": "clr",
|
||||
"linux": {
|
||||
"type": "mono"
|
||||
},
|
||||
"osx": {
|
||||
"type": "mono"
|
||||
},
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
"args": ["Game.Mod=ts", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
]
|
||||
}
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"omnisharp.enableRoslynAnalyzers": true
|
||||
}
|
||||
13
.vscode/tasks.json
vendored
Normal file
13
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "make",
|
||||
"args": ["all"],
|
||||
"windows": {
|
||||
"command": "make.cmd"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
18
AUTHORS
18
AUTHORS
@@ -40,6 +40,7 @@ Also thanks to:
|
||||
* Biofreak
|
||||
* Braxton Williams (Buddytex)
|
||||
* Brendan Gluth (Mechanical_Man)
|
||||
* Brent Gardner (bggardner)
|
||||
* Bryan Wilbur
|
||||
* Bugra Cuhadaroglu (BugraC)
|
||||
* Chris Cameron (Vesuvian)
|
||||
@@ -137,16 +138,19 @@ Also thanks to:
|
||||
* Sebastien Kerguen (xanax)
|
||||
* Shawn Collins (UberWaffe)
|
||||
* Simon Verbeke (Saticmotion)
|
||||
* Stuart McHattie (SDJMcHattie)
|
||||
* Taryn Hill (Phrohdoh)
|
||||
* Teemu Nieminen (Temeez)
|
||||
* Tim Mylemans (gecko)
|
||||
* Tirili
|
||||
* Tomas Einarsson (Mesacer)
|
||||
* Tom van Leth (tovl)
|
||||
* Trevor Nichols (ocdi)
|
||||
* Tristan Keating (Kilkakon)
|
||||
* Tristan Mühlbacher (MicroBit)
|
||||
* UnknownProgrammer
|
||||
* Vladimir Komarov (VrKomarov)
|
||||
* Wojciech Walaszek (Voidwalker)
|
||||
* Wuschel
|
||||
|
||||
Using GNU FreeFont distributed under the GNU GPL
|
||||
@@ -160,9 +164,6 @@ FreeType License.
|
||||
|
||||
Using OpenAL Soft distributed under the GNU LGPL.
|
||||
|
||||
Using MaxMind GeoIP2 .NET API distributed under
|
||||
the Apache 2.0 license.
|
||||
|
||||
Using SDL2-CS and OpenAL-CS created by Ethan
|
||||
Lee and released under the zlib license.
|
||||
|
||||
@@ -182,6 +183,17 @@ Krueger and distributed under the GNU GPL terms.
|
||||
Using rix0rrr.BeaconLib developed by Rico Huijbers
|
||||
distributed under MIT License.
|
||||
|
||||
Using DiscordRichPresence developed by Lachee
|
||||
distributed under MIT License.
|
||||
|
||||
Using Json.NET developed by James Newton-King
|
||||
distributed under MIT License.
|
||||
|
||||
Using ANGLE distributed under the BS3 3-Clause license.
|
||||
|
||||
This site or product includes IP2Location LITE data
|
||||
available from http://www.ip2location.com.
|
||||
|
||||
Finally, special thanks goes to the original teams
|
||||
at Westwood Studios and EA for creating the classic
|
||||
games which OpenRA aims to reimagine.
|
||||
|
||||
File diff suppressed because one or more lines are too long
41
INSTALL.md
41
INSTALL.md
@@ -8,15 +8,9 @@ Windows
|
||||
|
||||
Compiling OpenRA requires the following dependencies:
|
||||
* [Windows PowerShell >= 4.0](http://microsoft.com/powershell) (included by default in recent Windows 10 versions)
|
||||
* [.NET Framework 4.6.1 (Developer Pack)](https://dotnet.microsoft.com/download/dotnet-framework/net461) (or via Visual Studio 2017)
|
||||
* [.NET Framework 4.7.2 (Developer Pack)](https://dotnet.microsoft.com/download/dotnet-framework/net472) (or via Visual Studio 2017)
|
||||
* [.NET Core 2.2 SDK](https://dotnet.microsoft.com/download/dotnet-core/2.2) (or via Visual Studio 2017)
|
||||
|
||||
Type `make dependencies` in a command terminal to download pre-compiled native libraries for:
|
||||
* [SDL 2](http://www.libsdl.org/download-2.0.php)
|
||||
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm)
|
||||
* [zlib](http://gnuwin32.sourceforge.net/packages/zlib.htm)
|
||||
* [OpenAL](http://kcat.strangesoft.net/openal.html)
|
||||
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html)
|
||||
|
||||
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with MSBuild or use the Makefile analogue command `make all` scripted in PowerShell syntax.
|
||||
|
||||
@@ -25,13 +19,19 @@ Run the game with `launch-game.cmd`. It can be handed arguments that specify the
|
||||
Linux
|
||||
=====
|
||||
|
||||
Mono, version 5.4 or later, is required to compile OpenRA. You can add the [upstream mono repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version if your system packages are not sufficient.
|
||||
Mono, version 5.18 or later, is required to compile OpenRA. You can add the [upstream mono repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version if your system packages are not sufficient.
|
||||
|
||||
Use `make dependencies` to map the native libraries to your system and fetch the remaining CLI dependencies to place them at the appropriate places.
|
||||
To compile OpenRA, run `make` from the command line. After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
|
||||
|
||||
To compile OpenRA, run `make all` from the command line. After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
|
||||
The default behaviour on the x86_64 architecture is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`.
|
||||
|
||||
Type `sudo make install` for system-wide installation. Run `sudo make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run the Red Alert by executing the `openra-ra` command, the Dune 2000 mod by running the `openra-d2k` command and Tiberian Dawn by the `openra-cnc` command. Alternatively, you can also run these mods by clicking on their desktop shortcuts if you ran `sudo make install-linux-shortcuts`.
|
||||
If you choose to use system libraries, or your system is not x86_64, you will need to install the following using your system package manager:
|
||||
* [SDL 2](http://www.libsdl.org/download-2.0.php)
|
||||
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm)
|
||||
* [OpenAL](http://kcat.strangesoft.net/openal.html)
|
||||
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html)
|
||||
|
||||
Type `sudo make install` for system-wide installation. Run `sudo make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run the Red Alert by executing the `openra-ra` command, the Dune 2000 mod by running the `openra-d2k` command and Tiberian Dawn by the `openra-cnc` command. Alternatively, you can also run these mods by clicking on their desktop shortcuts if you ran `sudo make install-linux-shortcuts`.
|
||||
|
||||
Arch Linux
|
||||
----------
|
||||
@@ -89,24 +89,23 @@ sudo zypper in mono-devel openal-soft freetype2 SDL2 lua51 xdg-utils zenity
|
||||
Red Hat Enterprise Linux (and rebuilds, e.g. CentOS)
|
||||
----------------------------------------------------
|
||||
|
||||
The EPEL repository is required in order for the following command to run properly.
|
||||
The EPEL repository is required in order for the following command to run properly.
|
||||
|
||||
```
|
||||
sudo yum install "pkgconfig(mono)" SDL2 freetype "lua = 5.1" openal-soft xdg-utils zenity
|
||||
```
|
||||
|
||||
OSX
|
||||
macOS
|
||||
=====
|
||||
|
||||
Before compiling OpenRA you must install the following dependencies:
|
||||
* [Mono >= 5.4](https://www.mono-project.com/download/stable/#download-mac)
|
||||
* [Mono >= 5.18](https://www.mono-project.com/download/stable/#download-mac)
|
||||
|
||||
Use `make dependencies` to download pre-compiled native libraries for:
|
||||
* [SDL 2](http://www.libsdl.org/download-2.0.php)
|
||||
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm)
|
||||
* [OpenAL](http://kcat.strangesoft.net/openal.html)
|
||||
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html)
|
||||
To compile OpenRA, run `make` from the command line. Run with `./launch-game.sh`.
|
||||
|
||||
To compile OpenRA, run `make` from the command line.
|
||||
The default behaviour is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`. If you choose to use system libraries you will need to install:
|
||||
* [SDL 2](http://www.libsdl.org/download-2.0.php) (`brew install sdl2`)
|
||||
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm) (`brew install freetype`)
|
||||
* [OpenAL](http://kcat.strangesoft.net/openal.html) (`brew install openal-soft`)
|
||||
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html) (`brew install lua@5.1`)
|
||||
|
||||
Run with `./launch-game.sh`.
|
||||
|
||||
397
Makefile
397
Makefile
@@ -3,55 +3,47 @@
|
||||
# to compile, run:
|
||||
# make [DEBUG=true]
|
||||
#
|
||||
# to check unit tests (requires NUnit version >= 2.6), run:
|
||||
# make nunit [NUNIT_CONSOLE=<path-to/nunit[2]-console>] [NUNIT_LIBS_PATH=<path-to-libs-dir>] [NUNIT_LIBS=<nunit-libs>]
|
||||
# Use NUNIT_CONSOLE if nunit[3|2]-console was not downloaded by `make dependencies` nor is it in bin search paths
|
||||
# Use NUNIT_LIBS_PATH if NUnit libs are not in search paths. Include trailing /
|
||||
# Use NUNIT_LIBS if NUnit libs have different names (such as including a prefix or suffix)
|
||||
# to compile using system libraries for native dependencies, run:
|
||||
# make [DEBUG=true] TARGETPLATFORM=unix-generic
|
||||
#
|
||||
# to check the official mods for erroneous yaml files, run:
|
||||
# make test
|
||||
#
|
||||
# to check the official mod dlls for StyleCop violations, run:
|
||||
# to check the engine and official mod dlls for code style violations, run:
|
||||
# make check
|
||||
#
|
||||
# to install, run:
|
||||
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] install
|
||||
#
|
||||
# to install Linux startup scripts, desktop files and icons:
|
||||
# make install-linux-shortcuts [DEBUG=false]
|
||||
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000
|
||||
# using system libraries for native dependencies, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install
|
||||
#
|
||||
# to install the engine and common mod files (omitting the default mods):
|
||||
# make install-engine
|
||||
# make install-common-mod-files
|
||||
# to install Linux startup scripts, desktop files, icons, and MIME metadata
|
||||
# make install-linux-shortcuts
|
||||
#
|
||||
# to uninstall, run:
|
||||
# make uninstall
|
||||
# to install Linux AppStream metadata
|
||||
# make install-linux-appdata
|
||||
#
|
||||
# for help, run:
|
||||
# make help
|
||||
#
|
||||
# to start the game, run:
|
||||
# openra
|
||||
|
||||
############################## TOOLCHAIN ###############################
|
||||
#
|
||||
# List of .NET assemblies that we can guarantee exist
|
||||
# OpenRA.Game.dll is a harmless false positive that we can ignore
|
||||
WHITELISTED_OPENRA_ASSEMBLIES = OpenRA.Game.exe OpenRA.Utility.exe OpenRA.Platforms.Default.dll OpenRA.Mods.Common.dll OpenRA.Mods.Cnc.dll OpenRA.Mods.D2k.dll OpenRA.Game.dll
|
||||
WHITELISTED_OPENRA_ASSEMBLIES = OpenRA.exe OpenRA.Utility.exe OpenRA.Server.exe OpenRA.Platforms.Default.dll OpenRA.Game.dll OpenRA.Mods.Common.dll OpenRA.Mods.Cnc.dll OpenRA.Mods.D2k.dll
|
||||
|
||||
# These are explicitly shipped alongside our core files by the packaging script
|
||||
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll MaxMind.Db.dll Eluant.dll rix0rrr.BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.dll
|
||||
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll Eluant.dll BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.Core.dll DiscordRPC.dll Newtonsoft.Json.dll
|
||||
|
||||
# These are shipped in our custom minimal mono runtime and also available in the full system-installed .NET/mono stack
|
||||
# This list *must* be kept in sync with the files packaged by the AppImageSupport and OpenRALauncherOSX repositories
|
||||
WHITELISTED_CORE_ASSEMBLIES = mscorlib.dll System.dll System.Configuration.dll System.Core.dll System.Numerics.dll System.Security.dll System.Xml.dll Mono.Security.dll
|
||||
|
||||
NUNIT_LIBS_PATH :=
|
||||
NUNIT_LIBS := $(NUNIT_LIBS_PATH)nunit.framework.dll
|
||||
WHITELISTED_CORE_ASSEMBLIES = mscorlib.dll System.dll System.Configuration.dll System.Core.dll System.Numerics.dll System.Security.dll System.Xml.dll Mono.Security.dll netstandard.dll
|
||||
|
||||
######################### UTILITIES/SETTINGS ###########################
|
||||
#
|
||||
# install locations
|
||||
# Install locations for local installs and downstream packaging
|
||||
prefix ?= /usr/local
|
||||
datarootdir ?= $(prefix)/share
|
||||
datadir ?= $(datarootdir)
|
||||
@@ -60,321 +52,132 @@ bindir ?= $(prefix)/bin
|
||||
libdir ?= $(prefix)/lib
|
||||
gameinstalldir ?= $(libdir)/openra
|
||||
|
||||
BIN_INSTALL_DIR = $(DESTDIR)$(bindir)
|
||||
DATA_INSTALL_DIR = $(DESTDIR)$(gameinstalldir)
|
||||
|
||||
# install tools
|
||||
# Toolchain
|
||||
CWD = $(shell pwd)
|
||||
MSBUILD = msbuild -verbosity:m -nologo
|
||||
MONO = mono
|
||||
RM = rm
|
||||
RM_R = $(RM) -r
|
||||
RM_F = $(RM) -f
|
||||
RM_RF = $(RM) -rf
|
||||
CP = cp
|
||||
CP_R = $(CP) -r
|
||||
INSTALL = install
|
||||
INSTALL_DIR = $(INSTALL) -d
|
||||
INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
INSTALL_DATA = $(INSTALL) -m644
|
||||
|
||||
# Toolchain
|
||||
MSBUILD = msbuild -verbosity:m -nologo
|
||||
# Only for use in target version:
|
||||
VERSION := $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || (c=$$(git rev-parse --short HEAD 2>/dev/null) && echo git-$$c))
|
||||
|
||||
# Enable 32 bit builds while generating the windows installer
|
||||
WIN32 = false
|
||||
|
||||
# program targets
|
||||
VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`)
|
||||
|
||||
# dependencies
|
||||
# Detect target platform for dependencies if not given by the user
|
||||
ifndef TARGETPLATFORM
|
||||
UNAME_S := $(shell uname -s)
|
||||
UNAME_M := $(shell uname -m)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
os-dependencies = osx-dependencies
|
||||
TARGETPLATFORM = osx-x64
|
||||
else
|
||||
os-dependencies = linux-dependencies
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
TARGETPLATFORM = linux-x64
|
||||
else
|
||||
TARGETPLATFORM = unix-generic
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
OPENRA_UTILITY = ENGINE_DIR=".." $(MONO) --debug bin/OpenRA.Utility.exe
|
||||
|
||||
##################### DEVELOPMENT BUILDS AND TESTS #####################
|
||||
#
|
||||
all:
|
||||
@command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 5.18."; exit 1)
|
||||
@$(MSBUILD) -t:Build -restore -p:Configuration=Release -p:TargetPlatform=$(TARGETPLATFORM)
|
||||
ifeq ($(TARGETPLATFORM), unix-generic)
|
||||
@./configure-system-libraries.sh
|
||||
endif
|
||||
@./fetch-geoip.sh
|
||||
|
||||
clean:
|
||||
@-$(RM_RF) ./bin ./*/bin ./*/obj
|
||||
@$(MSBUILD) -t:Clean
|
||||
@-$(RM_F) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
|
||||
|
||||
check:
|
||||
@echo
|
||||
@echo "Compiling in debug mode..."
|
||||
@$(MSBUILD) -t:build -restore -p:Configuration=Debug -p:TargetPlatform=$(TARGETPLATFORM)
|
||||
ifeq ($(TARGETPLATFORM), unix-generic)
|
||||
@./configure-system-libraries.sh
|
||||
endif
|
||||
@echo
|
||||
@echo "Checking runtime assemblies..."
|
||||
@$(OPENRA_UTILITY) all --check-runtime-assemblies $(WHITELISTED_OPENRA_ASSEMBLIES) $(WHITELISTED_THIRDPARTY_ASSEMBLIES) $(WHITELISTED_CORE_ASSEMBLIES)
|
||||
@echo
|
||||
@echo "Checking for explicit interface violations..."
|
||||
@$(OPENRA_UTILITY) all --check-explicit-interfaces
|
||||
@echo
|
||||
@echo "Checking for incorrect conditional trait interface overrides..."
|
||||
@$(OPENRA_UTILITY) all --check-conditional-trait-interface-overrides
|
||||
|
||||
check-scripts:
|
||||
@echo
|
||||
@echo "Checking for Lua syntax errors..."
|
||||
@luac -p $(shell find mods/*/maps/* -iname '*.lua')
|
||||
@luac -p $(shell find lua/* -iname '*.lua')
|
||||
@luac -p $(shell find mods/*/bits/scripts/* -iname '*.lua')
|
||||
|
||||
check: dependencies
|
||||
@echo
|
||||
@echo "Compiling in debug mode..."
|
||||
@$(MSBUILD) -t:build -p:Configuration=Debug
|
||||
@echo
|
||||
@echo "Checking runtime assemblies..."
|
||||
@mono --debug OpenRA.Utility.exe all --check-runtime-assemblies $(WHITELISTED_OPENRA_ASSEMBLIES) $(WHITELISTED_THIRDPARTY_ASSEMBLIES) $(WHITELISTED_CORE_ASSEMBLIES)
|
||||
@echo
|
||||
@echo "Checking for explicit interface violations..."
|
||||
@mono --debug OpenRA.Utility.exe all --check-explicit-interfaces
|
||||
@echo
|
||||
@echo "Checking for incorrect conditional trait interface overrides..."
|
||||
@mono --debug OpenRA.Utility.exe all --check-conditional-trait-interface-overrides
|
||||
|
||||
|
||||
NUNIT_CONSOLE := $(shell test -f thirdparty/download/nunit3-console.exe && echo mono thirdparty/download/nunit3-console.exe || \
|
||||
which nunit3-console 2>/dev/null || which nunit2-console 2>/dev/null || which nunit-console 2>/dev/null)
|
||||
nunit: core
|
||||
@echo
|
||||
@echo "Checking unit tests..."
|
||||
@if [ "$(NUNIT_CONSOLE)" = "" ] ; then \
|
||||
echo 'nunit[3|2]-console not found!'; \
|
||||
echo 'Was "make dependencies" called or is NUnit installed?'>&2; \
|
||||
echo 'See "make help".'; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if $(NUNIT_CONSOLE) --help | head -n 1 | grep -E "NUnit version (1|2\.[0-5])";then \
|
||||
echo 'NUnit version >= 2.6 required'>&2; \
|
||||
echo 'Try "make dependencies" first to use NUnit from NuGet.'>&2; \
|
||||
echo 'See "make help".'; \
|
||||
exit 1; \
|
||||
fi
|
||||
@$(NUNIT_CONSOLE) --noresult OpenRA.Test.nunit
|
||||
|
||||
test: core
|
||||
test: all
|
||||
@echo
|
||||
@echo "Testing Tiberian Sun mod MiniYAML..."
|
||||
@mono --debug OpenRA.Utility.exe ts --check-yaml
|
||||
@$(OPENRA_UTILITY) ts --check-yaml
|
||||
@echo
|
||||
@echo "Testing Dune 2000 mod MiniYAML..."
|
||||
@mono --debug OpenRA.Utility.exe d2k --check-yaml
|
||||
@$(OPENRA_UTILITY) d2k --check-yaml
|
||||
@echo
|
||||
@echo "Testing Tiberian Dawn mod MiniYAML..."
|
||||
@mono --debug OpenRA.Utility.exe cnc --check-yaml
|
||||
@$(OPENRA_UTILITY) cnc --check-yaml
|
||||
@echo
|
||||
@echo "Testing Red Alert mod MiniYAML..."
|
||||
@mono --debug OpenRA.Utility.exe ra --check-yaml
|
||||
@$(OPENRA_UTILITY) ra --check-yaml
|
||||
|
||||
########################## MAKE/INSTALL RULES ##########################
|
||||
############# LOCAL INSTALLATION AND DOWNSTREAM PACKAGING ##############
|
||||
#
|
||||
all: dependencies core
|
||||
|
||||
core:
|
||||
@command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 5.4."; exit 1)
|
||||
ifeq ($(WIN32), $(filter $(WIN32),true yes y on 1))
|
||||
@$(MSBUILD) -t:build -p:Configuration="Release-x86"
|
||||
else
|
||||
@$(MSBUILD) -t:build -p:Configuration=Release
|
||||
endif
|
||||
|
||||
clean:
|
||||
@ $(MSBUILD) -t:clean
|
||||
@-$(RM_F) *.config
|
||||
@-$(RM_F) *.exe *.dll *.dylib ./OpenRA*/*.dll *.pdb mods/**/*.dll mods/**/*.pdb *.resources
|
||||
@-$(RM_RF) ./*/bin ./*/obj
|
||||
@-$(RM_RF) ./thirdparty/download
|
||||
|
||||
distclean: clean
|
||||
|
||||
cli-dependencies:
|
||||
@./thirdparty/fetch-thirdparty-deps.sh
|
||||
@ $(CP_R) thirdparty/download/*.dll .
|
||||
@ $(CP_R) thirdparty/download/*.dll.config .
|
||||
@ test -f OpenRA.Game/obj/project.assets.json || $(MSBUILD) -t:restore
|
||||
|
||||
linux-dependencies: cli-dependencies linux-native-dependencies
|
||||
|
||||
linux-native-dependencies:
|
||||
@./thirdparty/configure-native-deps.sh
|
||||
|
||||
windows-dependencies: cli-dependencies
|
||||
ifeq ($(WIN32), $(filter $(WIN32),true yes y on 1))
|
||||
@./thirdparty/fetch-thirdparty-deps-windows.sh x86
|
||||
else
|
||||
@./thirdparty/fetch-thirdparty-deps-windows.sh x64
|
||||
endif
|
||||
|
||||
osx-dependencies: cli-dependencies
|
||||
@./thirdparty/fetch-thirdparty-deps-osx.sh
|
||||
@ $(CP_R) thirdparty/download/osx/*.dylib .
|
||||
@ $(CP_R) thirdparty/download/osx/*.dll.config .
|
||||
|
||||
dependencies: $(os-dependencies)
|
||||
|
||||
all-dependencies: cli-dependencies windows-dependencies osx-dependencies
|
||||
|
||||
version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml
|
||||
@echo "$(VERSION)" > VERSION
|
||||
@for i in $? ; do \
|
||||
awk '{sub("Version:.*$$","Version: $(VERSION)"); print $0}' $${i} > $${i}.tmp && \
|
||||
awk '{sub("/[^/]*: User$$", "/$(VERSION): User"); print $0}' $${i}.tmp > $${i} && \
|
||||
rm $${i}.tmp; \
|
||||
done
|
||||
ifeq ($(VERSION),)
|
||||
$(error Unable to determine new version (requires git or override of variable VERSION))
|
||||
endif
|
||||
@sh -c '. ./packaging/functions.sh; set_engine_version "$(VERSION)" .'
|
||||
@sh -c '. ./packaging/functions.sh; set_mod_version "$(VERSION)" mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml'
|
||||
|
||||
install: dependencies core install-core
|
||||
install:
|
||||
@sh -c '. ./packaging/functions.sh; install_assemblies_mono $(CWD) $(DESTDIR)$(gameinstalldir) $(TARGETPLATFORM) True True True'
|
||||
@sh -c '. ./packaging/functions.sh; install_data $(CWD) $(DESTDIR)$(gameinstalldir) cnc d2k ra'
|
||||
|
||||
install-linux-shortcuts: install-linux-scripts install-linux-icons install-linux-desktop
|
||||
|
||||
install-engine:
|
||||
@-echo "Installing OpenRA engine to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) OpenRA.Game.exe "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) OpenRA.Server.exe "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) OpenRA.Utility.exe "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) OpenRA.Platforms.Default.dll "$(DATA_INSTALL_DIR)"
|
||||
|
||||
@$(INSTALL_DATA) OpenRA.Platforms.Default.dll.config "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DATA) VERSION "$(DATA_INSTALL_DIR)/VERSION"
|
||||
@$(INSTALL_DATA) AUTHORS "$(DATA_INSTALL_DIR)/AUTHORS"
|
||||
@$(INSTALL_DATA) COPYING "$(DATA_INSTALL_DIR)/COPYING"
|
||||
|
||||
@$(CP_R) glsl "$(DATA_INSTALL_DIR)"
|
||||
@$(CP_R) lua "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) SDL2-CS* "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) OpenAL-CS* "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) Eluant* "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) ICSharpCode.SharpZipLib.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) FuzzyLogicLibrary.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) Open.Nat.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) MaxMind.Db.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) rix0rrr.BeaconLib.dll "$(DATA_INSTALL_DIR)"
|
||||
|
||||
install-common-mod-files:
|
||||
@-echo "Installing OpenRA common mod files to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)/mods"
|
||||
@$(CP_R) mods/common "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(INSTALL_PROGRAM) mods/common/OpenRA.Mods.Common.dll "$(DATA_INSTALL_DIR)/mods/common"
|
||||
@$(INSTALL_PROGRAM) mods/common/OpenRA.Mods.Cnc.dll "$(DATA_INSTALL_DIR)/mods/common"
|
||||
@$(INSTALL_DATA) "global mix database.dat" "$(DATA_INSTALL_DIR)/global mix database.dat"
|
||||
|
||||
install-default-mods:
|
||||
@-echo "Installing OpenRA default mods to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)/mods"
|
||||
@$(CP_R) mods/cnc "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(CP_R) mods/ra "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(CP_R) mods/d2k "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(INSTALL_PROGRAM) mods/d2k/OpenRA.Mods.D2k.dll "$(DATA_INSTALL_DIR)/mods/d2k"
|
||||
@$(CP_R) mods/modcontent "$(DATA_INSTALL_DIR)/mods/"
|
||||
|
||||
install-core: install-engine install-common-mod-files install-default-mods
|
||||
@$(CP) *.sh "$(DATA_INSTALL_DIR)"
|
||||
|
||||
install-linux-icons:
|
||||
for SIZE in 16x16 32x32 48x48 64x64 128x128; do \
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps"; \
|
||||
$(INSTALL_DATA) packaging/linux/icons/ra_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-ra.png"; \
|
||||
$(INSTALL_DATA) packaging/linux/icons/cnc_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-cnc.png"; \
|
||||
$(INSTALL_DATA) packaging/linux/icons/d2k_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-d2k.png"; \
|
||||
done
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps"
|
||||
$(INSTALL_DATA) packaging/linux/icons/ra_scalable.svg "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-ra.svg"
|
||||
$(INSTALL_DATA) packaging/linux/icons/cnc_scalable.svg "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-cnc.svg"
|
||||
|
||||
install-linux-desktop:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/applications"
|
||||
@sed 's/{MODID}/ra/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Red Alert/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-ra.desktop
|
||||
@$(INSTALL_DATA) packaging/linux/openra-ra.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@sed 's/{MODID}/cnc/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Tiberian Dawn/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-cnc.desktop
|
||||
@$(INSTALL_DATA) packaging/linux/openra-cnc.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@sed 's/{MODID}/d2k/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Dune 2000/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-d2k.desktop
|
||||
@$(INSTALL_DATA) packaging/linux/openra-d2k.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@-$(RM) packaging/linux/openra-ra.desktop packaging/linux/openra-cnc.desktop packaging/linux/openra-d2k.desktop
|
||||
|
||||
install-linux-mime:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/mime/packages/"
|
||||
@sed 's/{MODID}/ra/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-ra.xml"
|
||||
@sed 's/{MODID}/cnc/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-cnc.xml"
|
||||
@sed 's/{MODID}/d2k/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-d2k.xml"
|
||||
install-linux-shortcuts:
|
||||
@sh -c '. ./packaging/functions.sh; install_linux_shortcuts $(CWD) "$(DESTDIR)" "$(gameinstalldir)" "$(bindir)" "$(datadir)" "$(shell head -n1 VERSION)" cnc d2k ra'
|
||||
|
||||
install-linux-appdata:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/appdata/"
|
||||
@sed 's/{MODID}/ra/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Red Alert/g' | sed 's/{SCREENSHOT_RA}/ type="default"/g' | sed 's/{SCREENSHOT_CNC}//g' | sed 's/{SCREENSHOT_D2K}//g'> packaging/linux/openra-ra.appdata.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-ra.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
|
||||
@sed 's/{MODID}/cnc/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Tiberian Dawn/g' | sed 's/{SCREENSHOT_RA}//g' | sed 's/{SCREENSHOT_CNC}/ type="default"/g' | sed 's/{SCREENSHOT_D2K}//g'> packaging/linux/openra-cnc.appdata.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-cnc.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
|
||||
@sed 's/{MODID}/d2k/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Dune 2000/g' | sed 's/{SCREENSHOT_RA}//g' | sed 's/{SCREENSHOT_CNC}//g' | sed 's/{SCREENSHOT_D2K}/ type="default"/g'> packaging/linux/openra-d2k.appdata.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-d2k.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
|
||||
@-$(RM) packaging/linux/openra-ra.appdata.xml packaging/linux/openra-cnc.appdata.xml packaging/linux/openra-d2k.appdata.xml
|
||||
|
||||
install-man-page:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man6/"
|
||||
@mono --debug OpenRA.Utility.exe all --man-page > openra.6
|
||||
@$(INSTALL_DATA) openra.6 "$(DESTDIR)$(mandir)/man6/"
|
||||
@-$(RM) openra.6
|
||||
|
||||
install-linux-scripts:
|
||||
ifeq ($(DEBUG), $(filter $(DEBUG),false no n off 0))
|
||||
@sed 's/{DEBUG}//' packaging/linux/openra.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra.debug.in
|
||||
@sed 's/{DEBUG}//' packaging/linux/openra-server.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra-server.debug.in
|
||||
else
|
||||
@sed 's/{DEBUG}/--debug/' packaging/linux/openra.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra.debug.in
|
||||
@sed 's/{DEBUG}/--debug/' packaging/linux/openra-server.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra-server.debug.in
|
||||
endif
|
||||
|
||||
@sed 's/{MODID}/ra/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Red Alert/g' > packaging/linux/openra-ra
|
||||
@sed 's/{MODID}/cnc/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Tiberian Dawn/g' > packaging/linux/openra-cnc
|
||||
@sed 's/{MODID}/d2k/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Dune 2000/g' > packaging/linux/openra-d2k
|
||||
|
||||
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-ra "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-cnc "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-d2k "$(BIN_INSTALL_DIR)"
|
||||
@-$(RM) packaging/linux/openra-ra packaging/linux/openra-cnc packaging/linux/openra-d2k packaging/linux/openra.debug.in
|
||||
|
||||
@sed 's/{MODID}/ra/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Red Alert/g' > packaging/linux/openra-ra-server
|
||||
@sed 's/{MODID}/cnc/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Tiberian Dawn/g' > packaging/linux/openra-cnc-server
|
||||
@sed 's/{MODID}/d2k/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Dune 2000/g' > packaging/linux/openra-d2k-server
|
||||
|
||||
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-ra-server "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-cnc-server "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-d2k-server "$(BIN_INSTALL_DIR)"
|
||||
@-$(RM) packaging/linux/openra-ra-server packaging/linux/openra-cnc-server packaging/linux/openra-d2k-server packaging/linux/openra-server.debug.in
|
||||
|
||||
uninstall:
|
||||
@-$(RM_R) "$(DATA_INSTALL_DIR)"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-ra"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-ra-server"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-cnc"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-cnc-server"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-d2k"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-d2k-server"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-ra.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-cnc.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-d2k.desktop"
|
||||
@-for SIZE in 16x16 32x32 48x48 64x64 128x128; do \
|
||||
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-ra.png"; \
|
||||
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-cnc.png"; \
|
||||
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-d2k.png"; \
|
||||
done
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-ra.svg"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-cnc.svg"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-ra.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-cnc.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-d2k.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-ra.appdata.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-cnc.appdata.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-d2k.appdata.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(mandir)/man6/openra.6"
|
||||
@sh -c '. ./packaging/functions.sh; install_linux_appdata $(CWD) "$(DESTDIR)" "$(datadir)" cnc d2k ra'
|
||||
|
||||
help:
|
||||
@echo 'to compile, run:'
|
||||
@echo ' make [DEBUG=false]'
|
||||
@echo ' make [DEBUG=true]'
|
||||
@echo
|
||||
@echo 'to check unit tests (requires NUnit version >= 2.6), run:'
|
||||
@echo ' make nunit [NUNIT_CONSOLE=<path-to/nunit[3|2]-console>] [NUNIT_LIBS_PATH=<path-to-libs-dir>] [NUNIT_LIBS=<nunit-libs>]'
|
||||
@echo ' Use NUNIT_CONSOLE if nunit[3|2]-console was not downloaded by `make dependencies` nor is it in bin search paths'
|
||||
@echo ' Use NUNIT_LIBS_PATH if NUnit libs are not in search paths. Include trailing /'
|
||||
@echo ' Use NUNIT_LIBS if NUnit libs have different names (such as including a prefix or suffix)'
|
||||
@echo 'to compile using system libraries for native dependencies, run:'
|
||||
@echo ' make [DEBUG=true] TARGETPLATFORM=unix-generic'
|
||||
@echo
|
||||
@echo 'to check the official mods for erroneous yaml files, run:'
|
||||
@echo ' make test'
|
||||
@echo
|
||||
@echo 'to install, run:'
|
||||
@echo ' make [prefix=/foo] [bindir=/bar/bin] install'
|
||||
@echo 'to check the engine and official mod dlls for code style violations, run:'
|
||||
@echo ' make test'
|
||||
@echo
|
||||
@echo 'to install Linux startup scripts, desktop files and icons'
|
||||
@echo ' make install-linux-shortcuts [DEBUG=false]'
|
||||
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000 run:'
|
||||
@echo ' make [prefix=/foo] [TARGETPLATFORM=unix-generic] install'
|
||||
@echo
|
||||
@echo 'to uninstall, run:'
|
||||
@echo ' make uninstall'
|
||||
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000'
|
||||
@echo 'using system libraries for native dependencies, run:'
|
||||
@echo ' make [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install'
|
||||
@echo
|
||||
@echo 'to start the game, run:'
|
||||
@echo ' openra'
|
||||
@echo 'to install Linux startup scripts, desktop files, icons, and MIME metadata'
|
||||
@echo ' make install-linux-shortcuts'
|
||||
@echo
|
||||
@echo 'to install Linux AppStream metadata'
|
||||
@echo ' make install-linux-appdata'
|
||||
|
||||
########################### MAKEFILE SETTINGS ##########################
|
||||
#
|
||||
@@ -382,4 +185,4 @@ help:
|
||||
|
||||
.SUFFIXES:
|
||||
|
||||
.PHONY: check-scripts check nunit test all core clean distclean cli-dependencies linux-dependencies linux-native-dependencies windows-dependencies osx-dependencies dependencies all-dependencies version install install-linux-shortcuts install-engine install-common-mod-files install-default-mods install-core install-linux-icons install-linux-desktop install-linux-mime install-linux-appdata install-man-page install-linux-scripts uninstall help
|
||||
.PHONY: all clean check check-scripts test version install install-linux-shortcuts install-linux-appdata help
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace OpenRA.Activities
|
||||
public readonly Color Color;
|
||||
public readonly Sprite Tile;
|
||||
|
||||
public TargetLineNode(Target target, Color color, Sprite tile = null)
|
||||
public TargetLineNode(in Target target, Color color, Sprite tile = null)
|
||||
{
|
||||
// Note: Not all activities are drawable. In that case, pass Target.Invalid as target,
|
||||
// if "yield break" in TargetLineNode(Actor self) is not feasible.
|
||||
@@ -185,8 +185,7 @@ namespace OpenRA.Activities
|
||||
/// </summary>
|
||||
internal void OnActorDisposeOuter(Actor self)
|
||||
{
|
||||
if (ChildActivity != null)
|
||||
ChildActivity.OnActorDisposeOuter(self);
|
||||
ChildActivity?.OnActorDisposeOuter(self);
|
||||
|
||||
OnActorDispose(self);
|
||||
}
|
||||
@@ -199,8 +198,7 @@ namespace OpenRA.Activities
|
||||
if (!IsInterruptible)
|
||||
return;
|
||||
|
||||
if (ChildActivity != null)
|
||||
ChildActivity.Cancel(self);
|
||||
ChildActivity?.Cancel(self);
|
||||
|
||||
// Directly mark activities that are queued and therefore didn't run yet as done
|
||||
State = State == ActivityState.Queued ? ActivityState.Done : ActivityState.Canceling;
|
||||
@@ -243,11 +241,9 @@ namespace OpenRA.Activities
|
||||
|
||||
Console.WriteLine(GetType().ToString().Split('.').Last());
|
||||
|
||||
if (ChildActivity != null)
|
||||
ChildActivity.PrintActivityTree(self, origin, level + 1);
|
||||
ChildActivity?.PrintActivityTree(self, origin, level + 1);
|
||||
|
||||
if (NextActivity != null)
|
||||
NextActivity.PrintActivityTree(self, origin, level);
|
||||
NextActivity?.PrintActivityTree(self, origin, level);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace OpenRA.Activities
|
||||
|
||||
public override bool Tick(Actor self)
|
||||
{
|
||||
if (a != null) a();
|
||||
a?.Invoke();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Eluant;
|
||||
using Eluant.ObjectBinding;
|
||||
@@ -68,16 +69,40 @@ namespace OpenRA
|
||||
{
|
||||
get
|
||||
{
|
||||
// TODO: Support non-zero pitch/roll in IFacing (IOrientation?)
|
||||
var facingValue = facing != null ? facing.Facing : 0;
|
||||
return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facingValue));
|
||||
return facing != null ? facing.Orientation : WRot.None;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Value used to represent an invalid token.</summary>
|
||||
public static readonly int InvalidConditionToken = -1;
|
||||
|
||||
class ConditionState
|
||||
{
|
||||
/// <summary>Delegates that have registered to be notified when this condition changes.</summary>
|
||||
public readonly List<VariableObserverNotifier> Notifiers = new List<VariableObserverNotifier>();
|
||||
|
||||
/// <summary>Unique integers identifying granted instances of the condition.</summary>
|
||||
public readonly HashSet<int> Tokens = new HashSet<int>();
|
||||
}
|
||||
|
||||
readonly Dictionary<string, ConditionState> conditionStates = new Dictionary<string, ConditionState>();
|
||||
|
||||
/// <summary>Each granted condition receives a unique token that is used when revoking.</summary>
|
||||
readonly Dictionary<int, string> conditionTokens = new Dictionary<int, string>();
|
||||
|
||||
int nextConditionToken = 1;
|
||||
|
||||
/// <summary>Cache of condition -> enabled state for quick evaluation of token counter conditions.</summary>
|
||||
readonly Dictionary<string, int> conditionCache = new Dictionary<string, int>();
|
||||
|
||||
/// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary>
|
||||
readonly IReadOnlyDictionary<string, int> readOnlyConditionCache;
|
||||
|
||||
internal SyncHash[] SyncHashes { get; private set; }
|
||||
|
||||
readonly IFacing facing;
|
||||
readonly IHealth health;
|
||||
readonly IResolveOrder[] resolveOrders;
|
||||
readonly IRenderModifier[] renderModifiers;
|
||||
readonly IRender[] renders;
|
||||
readonly IMouseBounds[] mouseBounds;
|
||||
@@ -85,18 +110,28 @@ namespace OpenRA
|
||||
readonly IDefaultVisibility defaultVisibility;
|
||||
readonly INotifyBecomingIdle[] becomingIdles;
|
||||
readonly INotifyIdle[] tickIdles;
|
||||
readonly ITargetablePositions[] targetablePositions;
|
||||
readonly IEnumerable<ITargetablePositions> enabledTargetablePositions;
|
||||
WPos[] staticTargetablePositions;
|
||||
bool created;
|
||||
bool setStaticTargetablePositions;
|
||||
|
||||
internal Actor(World world, string name, TypeDictionary initDict)
|
||||
{
|
||||
var duplicateInit = initDict.WithInterface<ISingleInstanceInit>().GroupBy(i => i.GetType())
|
||||
.FirstOrDefault(i => i.Count() > 1);
|
||||
|
||||
if (duplicateInit != null)
|
||||
throw new InvalidDataException("Duplicate initializer '{0}'".F(duplicateInit.Key.Name));
|
||||
|
||||
var init = new ActorInitializer(this, initDict);
|
||||
|
||||
readOnlyConditionCache = new ReadOnlyDictionary<string, int>(conditionCache);
|
||||
|
||||
World = world;
|
||||
ActorID = world.NextAID();
|
||||
if (initDict.Contains<OwnerInit>())
|
||||
Owner = init.Get<OwnerInit, Player>();
|
||||
var ownerInit = init.GetOrDefault<OwnerInit>();
|
||||
if (ownerInit != null)
|
||||
Owner = ownerInit.Value(world);
|
||||
|
||||
if (name != null)
|
||||
{
|
||||
@@ -106,51 +141,102 @@ namespace OpenRA
|
||||
throw new NotImplementedException("No rules definition for unit " + name);
|
||||
|
||||
Info = world.Map.Rules.Actors[name];
|
||||
foreach (var trait in Info.TraitsInConstructOrder())
|
||||
|
||||
IPositionable positionable = null;
|
||||
var resolveOrdersList = new List<IResolveOrder>();
|
||||
var renderModifiersList = new List<IRenderModifier>();
|
||||
var rendersList = new List<IRender>();
|
||||
var mouseBoundsList = new List<IMouseBounds>();
|
||||
var visibilityModifiersList = new List<IVisibilityModifier>();
|
||||
var becomingIdlesList = new List<INotifyBecomingIdle>();
|
||||
var tickIdlesList = new List<INotifyIdle>();
|
||||
var targetablesList = new List<ITargetable>();
|
||||
var targetablePositionsList = new List<ITargetablePositions>();
|
||||
var syncHashesList = new List<SyncHash>();
|
||||
|
||||
foreach (var traitInfo in Info.TraitsInConstructOrder())
|
||||
{
|
||||
AddTrait(trait.Create(init));
|
||||
var trait = traitInfo.Create(init);
|
||||
AddTrait(trait);
|
||||
|
||||
// Some traits rely on properties provided by IOccupySpace in their initialization,
|
||||
// so we must ready it now, we cannot wait until all traits have finished construction.
|
||||
if (trait is IOccupySpaceInfo)
|
||||
OccupiesSpace = Trait<IOccupySpace>();
|
||||
// PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per
|
||||
// actor that allows us to provide some fast implementations of commonly used methods that are relied on by
|
||||
// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
|
||||
// Note: The blocks are required to limit the scope of the t's, so we make an exception to our normal style
|
||||
// rules for spacing in order to keep these assignments compact and readable.
|
||||
{ if (trait is IPositionable t) positionable = t; }
|
||||
{ if (trait is IOccupySpace t) OccupiesSpace = t; }
|
||||
{ if (trait is IEffectiveOwner t) EffectiveOwner = t; }
|
||||
{ if (trait is IFacing t) facing = t; }
|
||||
{ if (trait is IHealth t) health = t; }
|
||||
{ if (trait is IResolveOrder t) resolveOrdersList.Add(t); }
|
||||
{ if (trait is IRenderModifier t) renderModifiersList.Add(t); }
|
||||
{ if (trait is IRender t) rendersList.Add(t); }
|
||||
{ if (trait is IMouseBounds t) mouseBoundsList.Add(t); }
|
||||
{ if (trait is IVisibilityModifier t) visibilityModifiersList.Add(t); }
|
||||
{ if (trait is IDefaultVisibility t) defaultVisibility = t; }
|
||||
{ if (trait is INotifyBecomingIdle t) becomingIdlesList.Add(t); }
|
||||
{ if (trait is INotifyIdle t) tickIdlesList.Add(t); }
|
||||
{ if (trait is ITargetable t) targetablesList.Add(t); }
|
||||
{ if (trait is ITargetablePositions t) targetablePositionsList.Add(t); }
|
||||
{ if (trait is ISync t) syncHashesList.Add(new SyncHash(t)); }
|
||||
}
|
||||
|
||||
resolveOrders = resolveOrdersList.ToArray();
|
||||
renderModifiers = renderModifiersList.ToArray();
|
||||
renders = rendersList.ToArray();
|
||||
mouseBounds = mouseBoundsList.ToArray();
|
||||
visibilityModifiers = visibilityModifiersList.ToArray();
|
||||
becomingIdles = becomingIdlesList.ToArray();
|
||||
tickIdles = tickIdlesList.ToArray();
|
||||
Targetables = targetablesList.ToArray();
|
||||
var targetablePositions = targetablePositionsList.ToArray();
|
||||
enabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled);
|
||||
SyncHashes = syncHashesList.ToArray();
|
||||
|
||||
setStaticTargetablePositions = positionable == null && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled);
|
||||
}
|
||||
|
||||
// PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per
|
||||
// actor that allows us to provide some fast implementations of commonly used methods that are relied on by
|
||||
// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
|
||||
EffectiveOwner = TraitOrDefault<IEffectiveOwner>();
|
||||
facing = TraitOrDefault<IFacing>();
|
||||
health = TraitOrDefault<IHealth>();
|
||||
renderModifiers = TraitsImplementing<IRenderModifier>().ToArray();
|
||||
renders = TraitsImplementing<IRender>().ToArray();
|
||||
mouseBounds = TraitsImplementing<IMouseBounds>().ToArray();
|
||||
visibilityModifiers = TraitsImplementing<IVisibilityModifier>().ToArray();
|
||||
defaultVisibility = Trait<IDefaultVisibility>();
|
||||
becomingIdles = TraitsImplementing<INotifyBecomingIdle>().ToArray();
|
||||
tickIdles = TraitsImplementing<INotifyIdle>().ToArray();
|
||||
Targetables = TraitsImplementing<ITargetable>().ToArray();
|
||||
targetablePositions = TraitsImplementing<ITargetablePositions>().ToArray();
|
||||
world.AddFrameEndTask(w =>
|
||||
{
|
||||
// Caching this in a AddFrameEndTask, because trait construction order might cause problems if done directly at creation time.
|
||||
// All actors that can move or teleport should have IPositionable, if not it's pretty safe to assume the actor is completely immobile and
|
||||
// all targetable positions can be cached if all ITargetablePositions have no conditional requirements.
|
||||
if (!Info.HasTraitInfo<IPositionableInfo>() && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled))
|
||||
staticTargetablePositions = targetablePositions.SelectMany(tp => tp.TargetablePositions(this)).ToArray();
|
||||
});
|
||||
|
||||
SyncHashes = TraitsImplementing<ISync>().Select(sync => new SyncHash(sync)).ToArray();
|
||||
}
|
||||
|
||||
internal void Created()
|
||||
internal void Initialize(bool addToWorld = true)
|
||||
{
|
||||
created = true;
|
||||
|
||||
// Make sure traits are usable for condition notifiers
|
||||
foreach (var t in TraitsImplementing<INotifyCreated>())
|
||||
t.Created(this);
|
||||
|
||||
var allObserverNotifiers = new HashSet<VariableObserverNotifier>();
|
||||
foreach (var provider in TraitsImplementing<IObservesVariables>())
|
||||
{
|
||||
foreach (var variableUser in provider.GetVariableObservers())
|
||||
{
|
||||
allObserverNotifiers.Add(variableUser.Notifier);
|
||||
foreach (var variable in variableUser.Variables)
|
||||
{
|
||||
var cs = conditionStates.GetOrAdd(variable);
|
||||
cs.Notifiers.Add(variableUser.Notifier);
|
||||
|
||||
// Initialize conditions that have not yet been granted to 0
|
||||
// NOTE: Some conditions may have already been granted by INotifyCreated calling GrantCondition,
|
||||
// and we choose to assign the token count to safely cover both cases instead of adding an if branch.
|
||||
conditionCache[variable] = cs.Tokens.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update all traits with their initial condition state
|
||||
foreach (var notify in allObserverNotifiers)
|
||||
notify(this, readOnlyConditionCache);
|
||||
|
||||
// All actors that can move or teleport should have IPositionable, if not it's pretty safe to assume the actor is completely immobile and
|
||||
// all targetable positions can be cached if all ITargetablePositions have no conditional requirements.
|
||||
if (setStaticTargetablePositions)
|
||||
staticTargetablePositions = enabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this)).ToArray();
|
||||
|
||||
// TODO: Other traits may need initialization after being notified of initial condition state.
|
||||
|
||||
// TODO: A post condition initialization notification phase may allow queueing activities instead.
|
||||
// The initial activity should run before any activities queued by INotifyCreated.Created
|
||||
// However, we need to know which traits are enabled (via conditions), so wait for after the calls and insert the activity as the first
|
||||
ICreationActivity creationActivity = null;
|
||||
@@ -171,6 +257,9 @@ namespace OpenRA
|
||||
activity.Queue(CurrentActivity);
|
||||
CurrentActivity = activity;
|
||||
}
|
||||
|
||||
if (addToWorld)
|
||||
World.Add(this);
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
@@ -233,7 +322,7 @@ namespace OpenRA
|
||||
yield return r;
|
||||
}
|
||||
|
||||
public Rectangle MouseBounds(WorldRenderer wr)
|
||||
public Polygon MouseBounds(WorldRenderer wr)
|
||||
{
|
||||
foreach (var mb in mouseBounds)
|
||||
{
|
||||
@@ -242,7 +331,7 @@ namespace OpenRA
|
||||
return bounds;
|
||||
}
|
||||
|
||||
return Rectangle.Empty;
|
||||
return Polygon.Empty;
|
||||
}
|
||||
|
||||
public void QueueActivity(bool queued, Activity nextActivity)
|
||||
@@ -266,8 +355,7 @@ namespace OpenRA
|
||||
|
||||
public void CancelActivity()
|
||||
{
|
||||
if (CurrentActivity != null)
|
||||
CurrentActivity.Cancel(this);
|
||||
CurrentActivity?.Cancel(this);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
@@ -319,8 +407,7 @@ namespace OpenRA
|
||||
{
|
||||
// If CurrentActivity isn't null, run OnActorDisposeOuter in case some cleanups are needed.
|
||||
// This should be done before the FrameEndTask to avoid dependency issues.
|
||||
if (CurrentActivity != null)
|
||||
CurrentActivity.OnActorDisposeOuter(this);
|
||||
CurrentActivity?.OnActorDisposeOuter(this);
|
||||
|
||||
// Allow traits/activities to prevent a race condition when they depend on disposing the actor (e.g. Transforms)
|
||||
WillDispose = true;
|
||||
@@ -339,11 +426,16 @@ namespace OpenRA
|
||||
World.TraitDict.RemoveActor(this);
|
||||
Disposed = true;
|
||||
|
||||
if (luaInterface != null)
|
||||
luaInterface.Value.OnActorDestroyed();
|
||||
luaInterface?.Value.OnActorDestroyed();
|
||||
});
|
||||
}
|
||||
|
||||
public void ResolveOrder(Order order)
|
||||
{
|
||||
foreach (var r in resolveOrders)
|
||||
r.ResolveOrder(this, order);
|
||||
}
|
||||
|
||||
// TODO: move elsewhere.
|
||||
public void ChangeOwner(Player newOwner)
|
||||
{
|
||||
@@ -447,13 +539,71 @@ namespace OpenRA
|
||||
if (staticTargetablePositions != null)
|
||||
return staticTargetablePositions;
|
||||
|
||||
var enabledTargetablePositionTraits = targetablePositions.Where(Exts.IsTraitEnabled);
|
||||
if (enabledTargetablePositionTraits.Any())
|
||||
return enabledTargetablePositionTraits.SelectMany(tp => tp.TargetablePositions(this));
|
||||
if (enabledTargetablePositions.Any())
|
||||
return enabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
|
||||
|
||||
return new[] { CenterPosition };
|
||||
}
|
||||
|
||||
#region Conditions
|
||||
|
||||
void UpdateConditionState(string condition, int token, bool isRevoke)
|
||||
{
|
||||
ConditionState conditionState = conditionStates.GetOrAdd(condition);
|
||||
|
||||
if (isRevoke)
|
||||
conditionState.Tokens.Remove(token);
|
||||
else
|
||||
conditionState.Tokens.Add(token);
|
||||
|
||||
conditionCache[condition] = conditionState.Tokens.Count;
|
||||
|
||||
// Conditions may be granted or revoked before the state is initialized.
|
||||
// These notifications will be processed after INotifyCreated.Created.
|
||||
if (created)
|
||||
foreach (var notify in conditionState.Notifiers)
|
||||
notify(this, readOnlyConditionCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grants a specified condition if it is valid.
|
||||
/// Otherwise, just returns InvalidConditionToken.
|
||||
/// </summary>
|
||||
/// <returns>The token that is used to revoke this condition.</returns>
|
||||
public int GrantCondition(string condition)
|
||||
{
|
||||
if (string.IsNullOrEmpty(condition))
|
||||
return InvalidConditionToken;
|
||||
|
||||
var token = nextConditionToken++;
|
||||
conditionTokens.Add(token, condition);
|
||||
UpdateConditionState(condition, token, false);
|
||||
return token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Revokes a previously granted condition.
|
||||
/// </summary>
|
||||
/// <param name="token">The token ID returned by GrantCondition.</param>
|
||||
/// <returns>The invalid token ID.</returns>
|
||||
public int RevokeCondition(int token)
|
||||
{
|
||||
if (!conditionTokens.TryGetValue(token, out var condition))
|
||||
throw new InvalidOperationException("Attempting to revoke condition with invalid token {0} for {1}.".F(token, this));
|
||||
|
||||
conditionTokens.Remove(token);
|
||||
UpdateConditionState(condition, token, true);
|
||||
return InvalidConditionToken;
|
||||
}
|
||||
|
||||
/// <summary>Returns whether the specified token is valid for RevokeCondition</summary>
|
||||
public bool TokenValid(int token)
|
||||
{
|
||||
return conditionTokens.ContainsKey(token);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Scripting interface
|
||||
|
||||
Lazy<ScriptActorInterface> luaInterface;
|
||||
@@ -471,8 +621,7 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
Actor a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
if (!left.TryGetClrValue(out Actor a) || !right.TryGetClrValue(out Actor b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
|
||||
@@ -89,9 +89,7 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
CPos a;
|
||||
CVec b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CVec b))
|
||||
throw new LuaException("Attempted to call CPos.Add(CPos, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
|
||||
return new LuaCustomClrObject(a + b);
|
||||
@@ -99,21 +97,18 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
CPos a;
|
||||
var rightType = right.WrappedClrType();
|
||||
if (!left.TryGetClrValue(out a))
|
||||
if (!left.TryGetClrValue(out CPos a))
|
||||
throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, rightType.Name));
|
||||
|
||||
if (rightType == typeof(CPos))
|
||||
{
|
||||
CPos b;
|
||||
right.TryGetClrValue(out b);
|
||||
right.TryGetClrValue(out CPos b);
|
||||
return new LuaCustomClrObject(a - b);
|
||||
}
|
||||
else if (rightType == typeof(CVec))
|
||||
{
|
||||
CVec b;
|
||||
right.TryGetClrValue(out b);
|
||||
right.TryGetClrValue(out CVec b);
|
||||
return new LuaCustomClrObject(a - b);
|
||||
}
|
||||
|
||||
@@ -122,8 +117,7 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
CPos a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CPos b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
|
||||
@@ -75,8 +75,7 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
CVec a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||
throw new LuaException("Attempted to call CVec.Add(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
|
||||
return new LuaCustomClrObject(a + b);
|
||||
@@ -84,8 +83,7 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
CVec a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||
throw new LuaException("Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
|
||||
|
||||
return new LuaCustomClrObject(a - b);
|
||||
@@ -98,8 +96,7 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
CVec a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
|
||||
20
OpenRA.Game/DefaultPlayer.cs
Normal file
20
OpenRA.Game/DefaultPlayer.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class DefaultPlayer : IGlobalModData
|
||||
{
|
||||
public readonly Color Color = Color.FromAhsl(0, 0, 238);
|
||||
}
|
||||
}
|
||||
@@ -90,8 +90,7 @@ namespace OpenRA
|
||||
public void CancelAsync()
|
||||
{
|
||||
lock (syncObject)
|
||||
if (wc != null)
|
||||
wc.CancelAsync();
|
||||
wc?.CancelAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,9 @@ namespace OpenRA
|
||||
|
||||
public ExternalMods()
|
||||
{
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, CreateSheet);
|
||||
// Don't try to load mod icons if we don't have a texture to put them in
|
||||
if (Game.Renderer != null)
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, CreateSheet);
|
||||
|
||||
// Several types of support directory types are available, depending on
|
||||
// how the player has installed and launched the game.
|
||||
@@ -94,20 +96,24 @@ namespace OpenRA
|
||||
void LoadMod(MiniYaml yaml, string path = null, bool forceRegistration = false)
|
||||
{
|
||||
var mod = FieldLoader.Load<ExternalMod>(yaml);
|
||||
var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon");
|
||||
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
|
||||
mod.Icon = sheetBuilder.Add(new Png(stream));
|
||||
|
||||
var icon2xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon2x");
|
||||
if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
|
||||
mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2);
|
||||
if (sheetBuilder != null)
|
||||
{
|
||||
var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon");
|
||||
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
|
||||
mod.Icon = sheetBuilder.Add(new Png(stream));
|
||||
|
||||
var icon3xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon3x");
|
||||
if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
|
||||
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);
|
||||
var icon2xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon2x");
|
||||
if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
|
||||
mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2);
|
||||
|
||||
var icon3xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon3x");
|
||||
if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
|
||||
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);
|
||||
}
|
||||
|
||||
// Avoid possibly overwriting a valid mod with an obviously bogus one
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
@@ -115,7 +121,7 @@ namespace OpenRA
|
||||
mods[key] = mod;
|
||||
}
|
||||
|
||||
internal void Register(Manifest mod, string launchPath, ModRegistration registration)
|
||||
internal void Register(Manifest mod, string launchPath, IEnumerable<string> launchArgs, ModRegistration registration)
|
||||
{
|
||||
if (mod.Metadata.Hidden)
|
||||
return;
|
||||
@@ -127,7 +133,7 @@ namespace OpenRA
|
||||
new MiniYamlNode("Version", mod.Metadata.Version),
|
||||
new MiniYamlNode("Title", mod.Metadata.Title),
|
||||
new MiniYamlNode("LaunchPath", launchPath),
|
||||
new MiniYamlNode("LaunchArgs", "Game.Mod=" + mod.Id)
|
||||
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
|
||||
}));
|
||||
|
||||
using (var stream = mod.Package.GetStream("icon.png"))
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace OpenRA
|
||||
|
||||
static int WindingDirectionTest(int2 v0, int2 v1, int2 p)
|
||||
{
|
||||
return (v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y);
|
||||
return Math.Sign((v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y));
|
||||
}
|
||||
|
||||
public static bool PolygonContains(this int2[] polygon, int2 p)
|
||||
@@ -101,6 +101,16 @@ namespace OpenRA
|
||||
return windingNumber != 0;
|
||||
}
|
||||
|
||||
public static bool LinesIntersect(int2 a, int2 b, int2 c, int2 d)
|
||||
{
|
||||
// If line segments AB and CD intersect:
|
||||
// - the triangles ACD and BCD must have opposite sense (clockwise or anticlockwise)
|
||||
// - the triangles CAB and DAB must have opposite sense
|
||||
// Segments intersect if the orientation (clockwise or anticlockwise) of the two points in each line segment are opposite with respect to the other
|
||||
// Assumes that lines are not colinear
|
||||
return WindingDirectionTest(c, d, a) != WindingDirectionTest(c, d, b) && WindingDirectionTest(a, b, c) != WindingDirectionTest(a, b, d);
|
||||
}
|
||||
|
||||
public static bool HasModifier(this Modifiers k, Modifiers mod)
|
||||
{
|
||||
// PERF: Enum.HasFlag is slower and requires allocations.
|
||||
@@ -110,13 +120,19 @@ namespace OpenRA
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k)
|
||||
where V : new()
|
||||
{
|
||||
return d.GetOrAdd(k, _ => new V());
|
||||
return d.GetOrAdd(k, new V());
|
||||
}
|
||||
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, V v)
|
||||
{
|
||||
if (!d.TryGetValue(k, out var ret))
|
||||
d.Add(k, ret = v);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, Func<K, V> createFn)
|
||||
{
|
||||
V ret;
|
||||
if (!d.TryGetValue(k, out ret))
|
||||
if (!d.TryGetValue(k, out var ret))
|
||||
d.Add(k, ret = createFn(k));
|
||||
return ret;
|
||||
}
|
||||
@@ -343,8 +359,7 @@ namespace OpenRA
|
||||
|
||||
public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor)
|
||||
{
|
||||
int remainder;
|
||||
var quotient = Math.DivRem(dividend, divisor, out remainder);
|
||||
var quotient = Math.DivRem(dividend, divisor, out var remainder);
|
||||
if (remainder == 0)
|
||||
return quotient;
|
||||
return quotient + (Math.Sign(dividend) == Math.Sign(divisor) ? 1 : -1);
|
||||
@@ -395,8 +410,7 @@ namespace OpenRA
|
||||
// Check for a key conflict:
|
||||
if (d.ContainsKey(key))
|
||||
{
|
||||
List<string> dupKeyMessages;
|
||||
if (!dupKeys.TryGetValue(key, out dupKeyMessages))
|
||||
if (!dupKeys.TryGetValue(key, out var dupKeyMessages))
|
||||
{
|
||||
// Log the initial conflicting value already inserted:
|
||||
dupKeyMessages = new List<string>();
|
||||
|
||||
@@ -18,7 +18,6 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
@@ -122,8 +121,7 @@ namespace OpenRA
|
||||
{
|
||||
ret = null;
|
||||
|
||||
MiniYaml yaml;
|
||||
if (!md.TryGetValue(yamlName, out yaml))
|
||||
if (!md.TryGetValue(yamlName, out var yaml))
|
||||
return false;
|
||||
|
||||
ret = GetValue(field.Name, field.FieldType, yaml, field);
|
||||
@@ -181,42 +179,36 @@ namespace OpenRA
|
||||
|
||||
public static object GetValue(string fieldName, Type fieldType, MiniYaml yaml, MemberInfo field)
|
||||
{
|
||||
var value = yaml.Value;
|
||||
if (value != null) value = value.Trim();
|
||||
var value = yaml.Value?.Trim();
|
||||
|
||||
if (fieldType == typeof(int))
|
||||
{
|
||||
int res;
|
||||
if (Exts.TryParseIntegerInvariant(value, out res))
|
||||
if (Exts.TryParseIntegerInvariant(value, out var res))
|
||||
return res;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(ushort))
|
||||
{
|
||||
ushort res;
|
||||
if (ushort.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out res))
|
||||
if (ushort.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
|
||||
return res;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
|
||||
if (fieldType == typeof(long))
|
||||
{
|
||||
long res;
|
||||
if (long.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out res))
|
||||
if (long.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
|
||||
return res;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(float))
|
||||
{
|
||||
float res;
|
||||
if (value != null && float.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
|
||||
if (value != null && float.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||
return res * (value.Contains('%') ? 0.01f : 1f);
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(decimal))
|
||||
{
|
||||
decimal res;
|
||||
if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
|
||||
if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||
return res * (value.Contains('%') ? 0.01m : 1m);
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
@@ -228,16 +220,14 @@ namespace OpenRA
|
||||
}
|
||||
else if (fieldType == typeof(Color))
|
||||
{
|
||||
Color color;
|
||||
if (value != null && Color.TryParse(value, out color))
|
||||
if (value != null && Color.TryParse(value, out var color))
|
||||
return color;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(Hotkey))
|
||||
{
|
||||
Hotkey res;
|
||||
if (Hotkey.TryParse(value, out res))
|
||||
if (Hotkey.TryParse(value, out var res))
|
||||
return res;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
@@ -248,8 +238,7 @@ namespace OpenRA
|
||||
}
|
||||
else if (fieldType == typeof(WDist))
|
||||
{
|
||||
WDist res;
|
||||
if (WDist.TryParse(value, out res))
|
||||
if (WDist.TryParse(value, out var res))
|
||||
return res;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
@@ -261,8 +250,7 @@ namespace OpenRA
|
||||
var parts = value.Split(',');
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
WDist rx, ry, rz;
|
||||
if (WDist.TryParse(parts[0], out rx) && WDist.TryParse(parts[1], out ry) && WDist.TryParse(parts[2], out rz))
|
||||
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
|
||||
return new WVec(rx, ry, rz);
|
||||
}
|
||||
}
|
||||
@@ -282,8 +270,7 @@ namespace OpenRA
|
||||
|
||||
for (var i = 0; i < vecs.Length; ++i)
|
||||
{
|
||||
WDist rx, ry, rz;
|
||||
if (WDist.TryParse(parts[3 * i], out rx) && WDist.TryParse(parts[3 * i + 1], out ry) && WDist.TryParse(parts[3 * i + 2], out rz))
|
||||
if (WDist.TryParse(parts[3 * i], out var rx) && WDist.TryParse(parts[3 * i + 1], out var ry) && WDist.TryParse(parts[3 * i + 2], out var rz))
|
||||
vecs[i] = new WVec(rx, ry, rz);
|
||||
}
|
||||
|
||||
@@ -299,8 +286,7 @@ namespace OpenRA
|
||||
var parts = value.Split(',');
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
WDist rx, ry, rz;
|
||||
if (WDist.TryParse(parts[0], out rx) && WDist.TryParse(parts[1], out ry) && WDist.TryParse(parts[2], out rz))
|
||||
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
|
||||
return new WPos(rx, ry, rz);
|
||||
}
|
||||
}
|
||||
@@ -309,8 +295,7 @@ namespace OpenRA
|
||||
}
|
||||
else if (fieldType == typeof(WAngle))
|
||||
{
|
||||
int res;
|
||||
if (Exts.TryParseIntegerInvariant(value, out res))
|
||||
if (Exts.TryParseIntegerInvariant(value, out var res))
|
||||
return new WAngle(res);
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
@@ -321,8 +306,7 @@ namespace OpenRA
|
||||
var parts = value.Split(',');
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
int rr, rp, ry;
|
||||
if (Exts.TryParseIntegerInvariant(parts[0], out rr) && Exts.TryParseIntegerInvariant(parts[1], out rp) && Exts.TryParseIntegerInvariant(parts[2], out ry))
|
||||
if (Exts.TryParseIntegerInvariant(parts[0], out var rr) && Exts.TryParseIntegerInvariant(parts[1], out var rp) && Exts.TryParseIntegerInvariant(parts[2], out var ry))
|
||||
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
|
||||
}
|
||||
}
|
||||
@@ -361,8 +345,7 @@ namespace OpenRA
|
||||
var vecs = new CVec[parts.Length / 2];
|
||||
for (var i = 0; i < vecs.Length; i++)
|
||||
{
|
||||
int rx, ry;
|
||||
if (int.TryParse(parts[2 * i], out rx) && int.TryParse(parts[2 * i + 1], out ry))
|
||||
if (int.TryParse(parts[2 * i], out var rx) && int.TryParse(parts[2 * i + 1], out var ry))
|
||||
vecs[i] = new CVec(rx, ry);
|
||||
}
|
||||
|
||||
@@ -416,8 +399,7 @@ namespace OpenRA
|
||||
}
|
||||
else if (fieldType == typeof(bool))
|
||||
{
|
||||
bool result;
|
||||
if (bool.TryParse(value.ToLowerInvariant(), out result))
|
||||
if (bool.TryParse(value.ToLowerInvariant(), out var result))
|
||||
return result;
|
||||
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
@@ -453,7 +435,7 @@ namespace OpenRA
|
||||
ret.SetValue(GetValue(fieldName, fieldType.GetElementType(), parts[i].Trim(), field), i);
|
||||
return ret;
|
||||
}
|
||||
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(HashSet<>))
|
||||
else if (fieldType.IsGenericType && (fieldType.GetGenericTypeDefinition() == typeof(HashSet<>) || fieldType.GetGenericTypeDefinition() == typeof(List<>)))
|
||||
{
|
||||
var set = Activator.CreateInstance(fieldType);
|
||||
if (value == null)
|
||||
@@ -510,8 +492,7 @@ namespace OpenRA
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
float xx = 0;
|
||||
float yy = 0;
|
||||
float res;
|
||||
if (float.TryParse(parts[0].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
|
||||
if (float.TryParse(parts[0].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
|
||||
xx = res * (parts[0].Contains('%') ? 0.01f : 1f);
|
||||
if (float.TryParse(parts[1].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
|
||||
yy = res * (parts[1].Contains('%') ? 0.01f : 1f);
|
||||
@@ -525,13 +506,11 @@ namespace OpenRA
|
||||
if (value != null)
|
||||
{
|
||||
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
float z = 0;
|
||||
float.TryParse(parts[0], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out x);
|
||||
float.TryParse(parts[1], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out y);
|
||||
float.TryParse(parts[0], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var x);
|
||||
float.TryParse(parts[1], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var y);
|
||||
|
||||
// z component is optional for compatibility with older float2 definitions
|
||||
float z = 0;
|
||||
if (parts.Length > 2)
|
||||
float.TryParse(parts[2], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out z);
|
||||
|
||||
@@ -567,14 +546,16 @@ namespace OpenRA
|
||||
}
|
||||
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return null;
|
||||
|
||||
var innerType = fieldType.GetGenericArguments().First();
|
||||
var innerValue = GetValue("Nullable<T>", innerType, value, field);
|
||||
return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue });
|
||||
}
|
||||
else if (fieldType == typeof(DateTime))
|
||||
{
|
||||
DateTime dt;
|
||||
if (DateTime.TryParseExact(value, "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out dt))
|
||||
if (DateTime.TryParseExact(value, "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var dt))
|
||||
return dt;
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
@@ -724,8 +705,7 @@ namespace OpenRA
|
||||
if (translations == null)
|
||||
return key;
|
||||
|
||||
string value;
|
||||
if (!translations.TryGetValue(key, out value))
|
||||
if (!translations.TryGetValue(key, out var value))
|
||||
return key;
|
||||
|
||||
return value;
|
||||
|
||||
@@ -15,7 +15,6 @@ using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
@@ -95,7 +94,7 @@ namespace OpenRA
|
||||
return ((Array)v).Cast<object>().Select(FormatValue).JoinWith(", ");
|
||||
}
|
||||
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(HashSet<>))
|
||||
if (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(HashSet<>) || t.GetGenericTypeDefinition() == typeof(List<>)))
|
||||
{
|
||||
return ((System.Collections.IEnumerable)v).Cast<object>().Select(FormatValue).JoinWith(", ");
|
||||
}
|
||||
|
||||
@@ -63,19 +63,16 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
// Raw directories are the easiest and one of the most common cases, so try these first
|
||||
var resolvedPath = Platform.ResolvePath(filename);
|
||||
if (!filename.Contains("|") && Directory.Exists(resolvedPath))
|
||||
if (!resolvedPath.Contains("|") && Directory.Exists(resolvedPath))
|
||||
return new Folder(resolvedPath);
|
||||
|
||||
// Children of another package require special handling
|
||||
IReadOnlyPackage parent;
|
||||
string subPath = null;
|
||||
if (TryGetPackageContaining(filename, out parent, out subPath))
|
||||
if (TryGetPackageContaining(filename, out var parent, out var subPath))
|
||||
return parent.OpenPackage(subPath, this);
|
||||
|
||||
// Try and open it normally
|
||||
IReadOnlyPackage package;
|
||||
var stream = Open(filename);
|
||||
if (TryParsePackage(stream, filename, out package))
|
||||
if (TryParsePackage(stream, filename, out var package))
|
||||
return package;
|
||||
|
||||
// No package loaders took ownership of the stream, so clean it up
|
||||
@@ -97,8 +94,7 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
name = name.Substring(1);
|
||||
|
||||
Manifest mod;
|
||||
if (!installedMods.TryGetValue(name, out mod))
|
||||
if (!installedMods.TryGetValue(name, out var mod))
|
||||
throw new InvalidOperationException("Could not load mod '{0}'. Available mods: {1}".F(name, installedMods.Keys.JoinWith(", ")));
|
||||
|
||||
package = mod.Package;
|
||||
@@ -122,8 +118,7 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public void Mount(IReadOnlyPackage package, string explicitName = null)
|
||||
{
|
||||
var mountCount = 0;
|
||||
if (mountedPackages.TryGetValue(package, out mountCount))
|
||||
if (mountedPackages.TryGetValue(package, out var mountCount))
|
||||
{
|
||||
// Package is already mounted
|
||||
// Increment the mount count and bump up the file loading priority
|
||||
@@ -149,8 +144,7 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public bool Unmount(IReadOnlyPackage package)
|
||||
{
|
||||
var mountCount = 0;
|
||||
if (!mountedPackages.TryGetValue(package, out mountCount))
|
||||
if (!mountedPackages.TryGetValue(package, out var mountCount))
|
||||
return false;
|
||||
|
||||
if (--mountCount <= 0)
|
||||
@@ -203,16 +197,12 @@ namespace OpenRA.FileSystem
|
||||
var package = fileIndex[filename]
|
||||
.LastOrDefault(x => x.Contains(filename));
|
||||
|
||||
if (package != null)
|
||||
return package.GetStream(filename);
|
||||
|
||||
return null;
|
||||
return package?.GetStream(filename);
|
||||
}
|
||||
|
||||
public Stream Open(string filename)
|
||||
{
|
||||
Stream s;
|
||||
if (!TryOpen(filename, out s))
|
||||
if (!TryOpen(filename, out var s))
|
||||
throw new FileNotFoundException("File not found: {0}".F(filename), filename);
|
||||
|
||||
return s;
|
||||
@@ -238,8 +228,7 @@ namespace OpenRA.FileSystem
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
{
|
||||
IReadOnlyPackage explicitPackage;
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
|
||||
{
|
||||
s = explicitPackage.GetStream(filename.Substring(explicitSplit + 1));
|
||||
if (s != null)
|
||||
@@ -274,12 +263,9 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
{
|
||||
IReadOnlyPackage explicitPackage;
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
|
||||
if (explicitPackage.Contains(filename.Substring(explicitSplit + 1)))
|
||||
return true;
|
||||
}
|
||||
|
||||
return fileIndex.ContainsKey(filename);
|
||||
}
|
||||
@@ -293,8 +279,7 @@ namespace OpenRA.FileSystem
|
||||
if (explicitSplit < 0)
|
||||
return false;
|
||||
|
||||
IReadOnlyPackage explicitPackage;
|
||||
if (!explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
|
||||
if (!explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
|
||||
return false;
|
||||
|
||||
if (installedMods[modID].Package == explicitPackage)
|
||||
@@ -310,7 +295,7 @@ namespace OpenRA.FileSystem
|
||||
public static string ResolveAssemblyPath(string path, Manifest manifest, InstalledMods installedMods)
|
||||
{
|
||||
var explicitSplit = path.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
if (explicitSplit > 0 && !path.StartsWith("^"))
|
||||
{
|
||||
var parent = path.Substring(0, explicitSplit);
|
||||
var filename = path.Substring(explicitSplit + 1);
|
||||
@@ -321,8 +306,7 @@ namespace OpenRA.FileSystem
|
||||
|
||||
if (parentPath.StartsWith("$", StringComparison.Ordinal))
|
||||
{
|
||||
Manifest mod;
|
||||
if (!installedMods.TryGetValue(parentPath.Substring(1), out mod))
|
||||
if (!installedMods.TryGetValue(parentPath.Substring(1), out var mod))
|
||||
return null;
|
||||
|
||||
if (!(mod.Package is Folder))
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
@@ -32,10 +33,11 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var filename in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly))
|
||||
yield return Path.GetFileName(filename);
|
||||
foreach (var filename in Directory.GetDirectories(path))
|
||||
yield return Path.GetFileName(filename);
|
||||
// Order may vary on different file systems and it matters for hashing.
|
||||
return Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly)
|
||||
.Concat(Directory.GetDirectories(path))
|
||||
.Select(Path.GetFileName)
|
||||
.OrderBy(f => f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,17 +60,15 @@ namespace OpenRA.FileSystem
|
||||
return new Folder(resolvedPath);
|
||||
|
||||
// Zip files loaded from Folders (and *only* from Folders) can be read-write
|
||||
IReadWritePackage readWritePackage;
|
||||
if (ZipFileLoader.TryParseReadWritePackage(resolvedPath, out readWritePackage))
|
||||
if (ZipFileLoader.TryParseReadWritePackage(resolvedPath, out var readWritePackage))
|
||||
return readWritePackage;
|
||||
|
||||
// Other package types can be loaded normally
|
||||
IReadOnlyPackage package;
|
||||
var s = GetStream(filename);
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
if (context.TryParsePackage(s, filename, out package))
|
||||
if (context.TryParsePackage(s, filename, out var package))
|
||||
return package;
|
||||
|
||||
s.Dispose();
|
||||
|
||||
@@ -14,7 +14,6 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
@@ -67,8 +66,7 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (pkg != null)
|
||||
pkg.Close();
|
||||
pkg?.Close();
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
@@ -82,12 +80,11 @@ namespace OpenRA.FileSystem
|
||||
return new ZipFolder(this, filename);
|
||||
|
||||
// Other package types can be loaded normally
|
||||
IReadOnlyPackage package;
|
||||
var s = GetStream(filename);
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
if (context.TryParsePackage(s, filename, out package))
|
||||
if (context.TryParsePackage(s, filename, out var package))
|
||||
return package;
|
||||
|
||||
s.Dispose();
|
||||
|
||||
@@ -17,6 +17,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Runtime;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.Graphics;
|
||||
@@ -51,7 +52,6 @@ namespace OpenRA
|
||||
|
||||
public static Renderer Renderer;
|
||||
public static Sound Sound;
|
||||
public static bool HasInputFocus = false;
|
||||
|
||||
public static string EngineVersion { get; private set; }
|
||||
public static LocalPlayerProfile LocalPlayerProfile;
|
||||
@@ -62,26 +62,26 @@ namespace OpenRA
|
||||
|
||||
public static event Action OnShellmapLoaded = () => { };
|
||||
|
||||
public static OrderManager JoinServer(string host, int port, string password, bool recordReplay = true)
|
||||
public static OrderManager JoinServer(ConnectionTarget endpoint, string password, bool recordReplay = true)
|
||||
{
|
||||
var connection = new NetworkConnection(host, port);
|
||||
var connection = new NetworkConnection(endpoint);
|
||||
if (recordReplay)
|
||||
connection.StartRecording(() => { return TimestampedFilename(); });
|
||||
|
||||
var om = new OrderManager(host, port, password, connection);
|
||||
var om = new OrderManager(endpoint, password, connection);
|
||||
JoinInner(om);
|
||||
return om;
|
||||
}
|
||||
|
||||
static string TimestampedFilename(bool includemilliseconds = false)
|
||||
public static string TimestampedFilename(bool includemilliseconds = false, string extra = "")
|
||||
{
|
||||
var format = includemilliseconds ? "yyyy-MM-ddTHHmmssfffZ" : "yyyy-MM-ddTHHmmssZ";
|
||||
return "OpenRA-" + DateTime.UtcNow.ToString(format, CultureInfo.InvariantCulture);
|
||||
return ModData.Manifest.Id + extra + "-" + DateTime.UtcNow.ToString(format, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
static void JoinInner(OrderManager om)
|
||||
{
|
||||
if (OrderManager != null) OrderManager.Dispose();
|
||||
OrderManager?.Dispose();
|
||||
OrderManager = om;
|
||||
lastConnectionState = ConnectionState.PreConnecting;
|
||||
ConnectionStateChanged(OrderManager);
|
||||
@@ -89,12 +89,12 @@ namespace OpenRA
|
||||
|
||||
public static void JoinReplay(string replayFile)
|
||||
{
|
||||
JoinInner(new OrderManager("<no server>", -1, "", new ReplayConnection(replayFile)));
|
||||
JoinInner(new OrderManager(new ConnectionTarget(), "", new ReplayConnection(replayFile)));
|
||||
}
|
||||
|
||||
static void JoinLocal()
|
||||
{
|
||||
JoinInner(new OrderManager("<no server>", -1, "", new EchoConnection()));
|
||||
JoinInner(new OrderManager(new ConnectionTarget(), "", new EchoConnection()));
|
||||
}
|
||||
|
||||
// More accurate replacement for Environment.TickCount
|
||||
@@ -105,14 +105,14 @@ namespace OpenRA
|
||||
public static int NetFrameNumber { get { return OrderManager.NetFrameNumber; } }
|
||||
public static int LocalTick { get { return OrderManager.LocalFrameNumber; } }
|
||||
|
||||
public static event Action<string, int> OnRemoteDirectConnect = (a, b) => { };
|
||||
public static event Action<ConnectionTarget> OnRemoteDirectConnect = _ => { };
|
||||
public static event Action<OrderManager> ConnectionStateChanged = _ => { };
|
||||
static ConnectionState lastConnectionState = ConnectionState.PreConnecting;
|
||||
public static int LocalClientId { get { return OrderManager.Connection.LocalClientId; } }
|
||||
|
||||
public static void RemoteDirectConnect(string host, int port)
|
||||
public static void RemoteDirectConnect(ConnectionTarget endpoint)
|
||||
{
|
||||
OnRemoteDirectConnect(host, port);
|
||||
OnRemoteDirectConnect(endpoint);
|
||||
}
|
||||
|
||||
// Hacky workaround for orderManager visibility
|
||||
@@ -155,8 +155,7 @@ namespace OpenRA
|
||||
internal static void StartGame(string mapUID, WorldType type)
|
||||
{
|
||||
// Dispose of the old world before creating a new one.
|
||||
if (worldRenderer != null)
|
||||
worldRenderer.Dispose();
|
||||
worldRenderer?.Dispose();
|
||||
|
||||
Cursor.SetCursor(null);
|
||||
BeforeGameStart();
|
||||
@@ -172,11 +171,13 @@ namespace OpenRA
|
||||
|
||||
worldRenderer = new WorldRenderer(ModData, OrderManager.World);
|
||||
|
||||
// Proactively collect memory during loading to reduce peak memory.
|
||||
GC.Collect();
|
||||
|
||||
using (new PerfTimer("LoadComplete"))
|
||||
OrderManager.World.LoadComplete(worldRenderer);
|
||||
|
||||
// Proactively collect memory during loading to reduce peak memory.
|
||||
GC.Collect();
|
||||
|
||||
if (OrderManager.GameStarted)
|
||||
@@ -191,6 +192,14 @@ namespace OpenRA
|
||||
worldRenderer.RefreshPalette();
|
||||
Cursor.SetCursor("default");
|
||||
|
||||
// Now loading is completed, now is the ideal time to run a GC and compact the LOH.
|
||||
// - All the temporary garbage created during loading can be collected.
|
||||
// - Live objects are likely to live for the length of the game or longer,
|
||||
// thus promoting them into a higher generation is not an issue.
|
||||
// - We can remove any fragmentation in the LOH caused by temporary loading garbage.
|
||||
// - A loading screen is visible, so a delay won't matter to the user.
|
||||
// Much better to clean up now then to drop frames during gameplay for GC pauses.
|
||||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
@@ -234,7 +243,7 @@ namespace OpenRA
|
||||
|
||||
LobbyInfoChanged += lobbyReady;
|
||||
|
||||
om = JoinServer(IPAddress.Loopback.ToString(), CreateLocalServer(mapUID), "");
|
||||
om = JoinServer(CreateLocalServer(mapUID), "");
|
||||
}
|
||||
|
||||
public static bool IsHost
|
||||
@@ -253,18 +262,24 @@ namespace OpenRA
|
||||
|
||||
public static void InitializeSettings(Arguments args)
|
||||
{
|
||||
Settings = new Settings(Platform.ResolvePath(Path.Combine(Platform.SupportDirPrefix, "settings.yaml")), args);
|
||||
Settings = new Settings(Path.Combine(Platform.SupportDir, "settings.yaml"), args);
|
||||
}
|
||||
|
||||
public static RunStatus InitializeAndRun(string[] args)
|
||||
{
|
||||
Initialize(new Arguments(args));
|
||||
|
||||
// Proactively collect memory during loading to reduce peak memory.
|
||||
GC.Collect();
|
||||
return Run();
|
||||
}
|
||||
|
||||
static void Initialize(Arguments args)
|
||||
{
|
||||
var engineDirArg = args.GetValue("Engine.EngineDir", null);
|
||||
if (!string.IsNullOrEmpty(engineDirArg))
|
||||
Platform.OverrideEngineDir(engineDirArg);
|
||||
|
||||
var supportDirArg = args.GetValue("Engine.SupportDir", null);
|
||||
if (!string.IsNullOrEmpty(supportDirArg))
|
||||
Platform.OverrideSupportDir(supportDirArg);
|
||||
@@ -274,7 +289,7 @@ namespace OpenRA
|
||||
// Load the engine version as early as possible so it can be written to exception logs
|
||||
try
|
||||
{
|
||||
EngineVersion = File.ReadAllText(Platform.ResolvePath(Path.Combine(".", "VERSION"))).Trim();
|
||||
EngineVersion = File.ReadAllText(Path.Combine(Platform.EngineDir, "VERSION")).Trim();
|
||||
}
|
||||
catch { }
|
||||
|
||||
@@ -302,6 +317,7 @@ namespace OpenRA
|
||||
Log.AddChannel("graphics", "graphics.log");
|
||||
Log.AddChannel("geoip", "geoip.log");
|
||||
Log.AddChannel("nat", "nat.log");
|
||||
Log.AddChannel("client", "client.log");
|
||||
|
||||
var platforms = new[] { Settings.Game.Platform, "Default", null };
|
||||
foreach (var p in platforms)
|
||||
@@ -312,7 +328,7 @@ namespace OpenRA
|
||||
Settings.Game.Platform = p;
|
||||
try
|
||||
{
|
||||
var rendererPath = Platform.ResolvePath(Path.Combine(".", "OpenRA.Platforms." + p + ".dll"));
|
||||
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + p + ".dll");
|
||||
var assembly = Assembly.LoadFile(rendererPath);
|
||||
|
||||
var platformType = assembly.GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
||||
@@ -330,11 +346,9 @@ namespace OpenRA
|
||||
Log.Write("graphics", "{0}", e);
|
||||
Console.WriteLine("Renderer initialization failed. Check graphics.log for details.");
|
||||
|
||||
if (Renderer != null)
|
||||
Renderer.Dispose();
|
||||
Renderer?.Dispose();
|
||||
|
||||
if (Sound != null)
|
||||
Sound.Dispose();
|
||||
Sound?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +358,7 @@ namespace OpenRA
|
||||
var modSearchArg = args.GetValue("Engine.ModSearchPaths", null);
|
||||
var modSearchPaths = modSearchArg != null ?
|
||||
FieldLoader.GetValue<string[]>("Engine.ModsPath", modSearchArg) :
|
||||
new[] { Path.Combine(".", "mods") };
|
||||
new[] { Path.Combine(Platform.EngineDir, "mods") };
|
||||
|
||||
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
|
||||
Console.WriteLine("Internal mods:");
|
||||
@@ -355,20 +369,28 @@ namespace OpenRA
|
||||
|
||||
ExternalMods = new ExternalMods();
|
||||
|
||||
Manifest currentMod;
|
||||
if (modID != null && Mods.TryGetValue(modID, out currentMod))
|
||||
if (modID != null && Mods.TryGetValue(modID, out _))
|
||||
{
|
||||
var launchPath = args.GetValue("Engine.LaunchPath", Assembly.GetEntryAssembly().Location);
|
||||
var launchPath = args.GetValue("Engine.LaunchPath", null);
|
||||
var launchArgs = new List<string>();
|
||||
|
||||
// Sanitize input from platform-specific launchers
|
||||
// Process.Start requires paths to not be quoted, even if they contain spaces
|
||||
if (launchPath.First() == '"' && launchPath.Last() == '"')
|
||||
if (launchPath != null && launchPath.First() == '"' && launchPath.Last() == '"')
|
||||
launchPath = launchPath.Substring(1, launchPath.Length - 2);
|
||||
|
||||
ExternalMods.Register(Mods[modID], launchPath, ModRegistration.User);
|
||||
if (launchPath == null)
|
||||
{
|
||||
// When launching the assembly directly we must propagate the Engine.EngineDir argument if defined
|
||||
// Platform-specific launchers are expected to manage this internally.
|
||||
launchPath = Assembly.GetEntryAssembly().Location;
|
||||
if (!string.IsNullOrEmpty(engineDirArg))
|
||||
launchArgs.Add("Engine.EngineDir=\"" + engineDirArg + "\"");
|
||||
}
|
||||
|
||||
ExternalMod activeMod;
|
||||
if (ExternalMods.TryGetValue(ExternalMod.MakeKey(Mods[modID]), out activeMod))
|
||||
ExternalMods.Register(Mods[modID], launchPath, launchArgs, ModRegistration.User);
|
||||
|
||||
if (ExternalMods.TryGetValue(ExternalMod.MakeKey(Mods[modID]), out var activeMod))
|
||||
ExternalMods.ClearInvalidRegistrations(activeMod, ModRegistration.User);
|
||||
}
|
||||
|
||||
@@ -385,18 +407,15 @@ namespace OpenRA
|
||||
LobbyInfoChanged = () => { };
|
||||
ConnectionStateChanged = om => { };
|
||||
BeforeGameStart = () => { };
|
||||
OnRemoteDirectConnect = (a, b) => { };
|
||||
OnRemoteDirectConnect = endpoint => { };
|
||||
delayedActions = new ActionQueue();
|
||||
|
||||
Ui.ResetAll();
|
||||
|
||||
if (worldRenderer != null)
|
||||
worldRenderer.Dispose();
|
||||
worldRenderer?.Dispose();
|
||||
worldRenderer = null;
|
||||
if (server != null)
|
||||
server.Shutdown();
|
||||
if (OrderManager != null)
|
||||
OrderManager.Dispose();
|
||||
server?.Shutdown();
|
||||
OrderManager?.Dispose();
|
||||
|
||||
if (ModData != null)
|
||||
{
|
||||
@@ -418,7 +437,7 @@ namespace OpenRA
|
||||
|
||||
ModData = new ModData(Mods[mod], Mods, true);
|
||||
|
||||
LocalPlayerProfile = new LocalPlayerProfile(Platform.ResolvePath(Path.Combine("^", Settings.Game.AuthProfile)), ModData.Manifest.Get<PlayerDatabase>());
|
||||
LocalPlayerProfile = new LocalPlayerProfile(Path.Combine(Platform.SupportDir, Settings.Game.AuthProfile), ModData.Manifest.Get<PlayerDatabase>());
|
||||
|
||||
if (!ModData.LoadScreen.BeforeLoad())
|
||||
return;
|
||||
@@ -432,8 +451,7 @@ namespace OpenRA
|
||||
var grid = ModData.Manifest.Contains<MapGrid>() ? ModData.Manifest.Get<MapGrid>() : null;
|
||||
Renderer.InitializeDepthBuffer(grid);
|
||||
|
||||
if (Cursor != null)
|
||||
Cursor.Dispose();
|
||||
Cursor?.Dispose();
|
||||
|
||||
Cursor = new CursorManager(ModData.CursorProvider);
|
||||
|
||||
@@ -442,13 +460,13 @@ namespace OpenRA
|
||||
PerfHistory.Items["render_world"].HasNormalTick = false;
|
||||
PerfHistory.Items["render_widgets"].HasNormalTick = false;
|
||||
PerfHistory.Items["render_flip"].HasNormalTick = false;
|
||||
PerfHistory.Items["terrain_lighting"].HasNormalTick = false;
|
||||
|
||||
JoinLocal();
|
||||
|
||||
try
|
||||
{
|
||||
if (discoverNat != null)
|
||||
discoverNat.Wait();
|
||||
discoverNat?.Wait();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -535,7 +553,7 @@ namespace OpenRA
|
||||
using (new PerfTimer("Renderer.SaveScreenshot"))
|
||||
{
|
||||
var mod = ModData.Manifest.Metadata;
|
||||
var directory = Platform.ResolvePath(Platform.SupportDirPrefix, "Screenshots", ModData.Manifest.Id, mod.Version);
|
||||
var directory = Path.Combine(Platform.SupportDir, "Screenshots", ModData.Manifest.Id, mod.Version);
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
var filename = TimestampedFilename(true);
|
||||
@@ -611,8 +629,7 @@ namespace OpenRA
|
||||
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, () => world.TickRender(worldRenderer));
|
||||
}
|
||||
|
||||
if (benchmark != null)
|
||||
benchmark.Tick(LocalTick);
|
||||
benchmark?.Tick(LocalTick);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -710,6 +727,7 @@ namespace OpenRA
|
||||
PerfHistory.Items["render_world"].Tick();
|
||||
PerfHistory.Items["render_widgets"].Tick();
|
||||
PerfHistory.Items["render_flip"].Tick();
|
||||
PerfHistory.Items["terrain_lighting"].Tick();
|
||||
}
|
||||
|
||||
static void Loop()
|
||||
@@ -836,12 +854,10 @@ namespace OpenRA
|
||||
finally
|
||||
{
|
||||
// Ensure that the active replay is properly saved
|
||||
if (OrderManager != null)
|
||||
OrderManager.Dispose();
|
||||
OrderManager?.Dispose();
|
||||
}
|
||||
|
||||
if (worldRenderer != null)
|
||||
worldRenderer.Dispose();
|
||||
worldRenderer?.Dispose();
|
||||
ModData.Dispose();
|
||||
ChromeProvider.Deinitialize();
|
||||
|
||||
@@ -880,8 +896,7 @@ namespace OpenRA
|
||||
|
||||
public static void Disconnect()
|
||||
{
|
||||
if (OrderManager.World != null)
|
||||
OrderManager.World.TraitDict.PrintReport();
|
||||
OrderManager.World?.TraitDict.PrintReport();
|
||||
|
||||
OrderManager.Dispose();
|
||||
CloseServer();
|
||||
@@ -890,8 +905,7 @@ namespace OpenRA
|
||||
|
||||
public static void CloseServer()
|
||||
{
|
||||
if (server != null)
|
||||
server.Shutdown();
|
||||
server?.Shutdown();
|
||||
}
|
||||
|
||||
public static T CreateObject<T>(string name)
|
||||
@@ -899,12 +913,19 @@ namespace OpenRA
|
||||
return ModData.ObjectCreator.CreateObject<T>(name);
|
||||
}
|
||||
|
||||
public static void CreateServer(ServerSettings settings)
|
||||
public static ConnectionTarget CreateServer(ServerSettings settings)
|
||||
{
|
||||
server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, ModData, ServerType.Multiplayer);
|
||||
var endpoints = new List<IPEndPoint>
|
||||
{
|
||||
new IPEndPoint(IPAddress.IPv6Any, settings.ListenPort),
|
||||
new IPEndPoint(IPAddress.Any, settings.ListenPort)
|
||||
};
|
||||
server = new Server.Server(endpoints, settings, ModData, ServerType.Multiplayer);
|
||||
|
||||
return server.GetEndpointForLocalConnection();
|
||||
}
|
||||
|
||||
public static int CreateLocalServer(string map)
|
||||
public static ConnectionTarget CreateLocalServer(string map)
|
||||
{
|
||||
var settings = new ServerSettings()
|
||||
{
|
||||
@@ -913,9 +934,16 @@ namespace OpenRA
|
||||
AdvertiseOnline = false
|
||||
};
|
||||
|
||||
server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), settings, ModData, ServerType.Local);
|
||||
// Always connect to local games using the same loopback connection
|
||||
// Exposing multiple endpoints introduces a race condition on the client's PlayerIndex (sometimes 0, sometimes 1)
|
||||
// This would break the Restart button, which relies on the PlayerIndex always being the same for local servers
|
||||
var endpoints = new List<IPEndPoint>
|
||||
{
|
||||
new IPEndPoint(IPAddress.Loopback, 0)
|
||||
};
|
||||
server = new Server.Server(endpoints, settings, ModData, ServerType.Local);
|
||||
|
||||
return server.Port;
|
||||
return server.GetEndpointForLocalConnection();
|
||||
}
|
||||
|
||||
public static bool IsCurrentWorld(World world)
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace OpenRA
|
||||
/// <summary>Gets the game's duration, from the time the game started until the replay recording stopped.</summary>
|
||||
public TimeSpan Duration { get { return EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero; } }
|
||||
public IList<Player> Players { get; private set; }
|
||||
public HashSet<int> DisabledSpawnPoints = new HashSet<int>();
|
||||
public MapPreview MapPreview { get { return Game.ModData.MapCache[MapUid]; } }
|
||||
public IEnumerable<Player> HumanPlayers { get { return Players.Where(p => p.IsHuman); } }
|
||||
public bool IsSinglePlayer { get { return HumanPlayers.Count() == 1; } }
|
||||
@@ -118,11 +119,14 @@ namespace OpenRA
|
||||
IsBot = runtimePlayer.IsBot,
|
||||
FactionName = runtimePlayer.Faction.Name,
|
||||
FactionId = runtimePlayer.Faction.InternalName,
|
||||
DisplayFactionName = runtimePlayer.DisplayFaction.Name,
|
||||
DisplayFactionId = runtimePlayer.DisplayFaction.InternalName,
|
||||
Color = runtimePlayer.Color,
|
||||
Team = client.Team,
|
||||
Handicap = client.Handicap,
|
||||
SpawnPoint = runtimePlayer.SpawnPoint,
|
||||
IsRandomFaction = runtimePlayer.Faction.InternalName != client.Faction,
|
||||
IsRandomSpawnPoint = runtimePlayer.SpawnPoint != client.SpawnPoint,
|
||||
IsRandomSpawnPoint = runtimePlayer.DisplaySpawnPoint == 0,
|
||||
Fingerprint = client.Fingerprint
|
||||
};
|
||||
|
||||
@@ -133,9 +137,7 @@ namespace OpenRA
|
||||
/// <summary>Gets the player information for the specified runtime player instance.</summary>
|
||||
public Player GetPlayer(OpenRA.Player runtimePlayer)
|
||||
{
|
||||
Player player;
|
||||
|
||||
playersByRuntime.TryGetValue(runtimePlayer, out player);
|
||||
playersByRuntime.TryGetValue(runtimePlayer, out var player);
|
||||
|
||||
return player;
|
||||
}
|
||||
@@ -158,9 +160,14 @@ namespace OpenRA
|
||||
public string FactionId;
|
||||
public Color Color;
|
||||
|
||||
/// <summary>The faction (including Random, etc.) that was selected in the lobby.</summary>
|
||||
public string DisplayFactionName;
|
||||
public string DisplayFactionId;
|
||||
|
||||
/// <summary>The team ID on start-up, or 0 if the player is not part of a team.</summary>
|
||||
public int Team;
|
||||
public int SpawnPoint;
|
||||
public int Handicap;
|
||||
|
||||
/// <summary>True if the faction was chosen at random; otherwise, false.</summary>
|
||||
public bool IsRandomFaction;
|
||||
@@ -181,6 +188,9 @@ namespace OpenRA
|
||||
/// <summary>The time when this player won or lost the game.</summary>
|
||||
public DateTime OutcomeTimestampUtc;
|
||||
|
||||
/// <summary>The frame at which this player disconnected.</summary>
|
||||
public int DisconnectFrame;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace OpenRA
|
||||
public class ActorInfo
|
||||
{
|
||||
public const string AbstractActorPrefix = "^";
|
||||
public const char TraitInstanceSeparator = '@';
|
||||
|
||||
/// <summary>
|
||||
/// The actor name can be anything, but the sprites used in the Render*: traits default to this one.
|
||||
@@ -32,7 +33,7 @@ namespace OpenRA
|
||||
/// </summary>
|
||||
public readonly string Name;
|
||||
readonly TypeDictionary traits = new TypeDictionary();
|
||||
List<ITraitInfo> constructOrderCache = null;
|
||||
List<TraitInfo> constructOrderCache = null;
|
||||
|
||||
public ActorInfo(ObjectCreator creator, string name, MiniYaml node)
|
||||
{
|
||||
@@ -46,7 +47,7 @@ namespace OpenRA
|
||||
{
|
||||
// HACK: The linter does not want to crash when a trait doesn't exist but only print an error instead
|
||||
// LoadTraitInfo will only return null to signal us to abort here if the linter is running
|
||||
var trait = LoadTraitInfo(creator, t.Key.Split('@')[0], t.Value);
|
||||
var trait = LoadTraitInfo(creator, t.Key, t.Value);
|
||||
if (trait != null)
|
||||
traits.Add(trait);
|
||||
}
|
||||
@@ -64,7 +65,7 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
public ActorInfo(string name, params ITraitInfo[] traitInfos)
|
||||
public ActorInfo(string name, params TraitInfo[] traitInfos)
|
||||
{
|
||||
Name = name;
|
||||
foreach (var t in traitInfos)
|
||||
@@ -72,7 +73,7 @@ namespace OpenRA
|
||||
traits.TrimExcess();
|
||||
}
|
||||
|
||||
static ITraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my)
|
||||
static TraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(my.Value))
|
||||
throw new YamlException("Junk value `{0}` on trait node {1}"
|
||||
@@ -80,12 +81,16 @@ namespace OpenRA
|
||||
|
||||
// HACK: The linter does not want to crash when a trait doesn't exist but only print an error instead
|
||||
// ObjectCreator will only return null to signal us to abort here if the linter is running
|
||||
var info = creator.CreateObject<ITraitInfo>(traitName + "Info");
|
||||
var traitInstance = traitName.Split(TraitInstanceSeparator);
|
||||
var info = creator.CreateObject<TraitInfo>(traitInstance[0] + "Info");
|
||||
if (info == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
if (traitInstance.Length > 1)
|
||||
info.GetType().GetField("InstanceName").SetValue(info, traitInstance[1]);
|
||||
|
||||
FieldLoader.Load(info, my);
|
||||
}
|
||||
catch (FieldLoader.MissingFieldsException e)
|
||||
@@ -97,12 +102,12 @@ namespace OpenRA
|
||||
return info;
|
||||
}
|
||||
|
||||
public IEnumerable<ITraitInfo> TraitsInConstructOrder()
|
||||
public IEnumerable<TraitInfo> TraitsInConstructOrder()
|
||||
{
|
||||
if (constructOrderCache != null)
|
||||
return constructOrderCache;
|
||||
|
||||
var source = traits.WithInterface<ITraitInfo>().Select(i => new
|
||||
var source = traits.WithInterface<TraitInfo>().Select(i => new
|
||||
{
|
||||
Trait = i,
|
||||
Type = i.GetType(),
|
||||
@@ -148,7 +153,7 @@ namespace OpenRA
|
||||
return constructOrderCache;
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> PrerequisitesOf(ITraitInfo info)
|
||||
public static IEnumerable<Type> PrerequisitesOf(TraitInfo info)
|
||||
{
|
||||
return info
|
||||
.GetType()
|
||||
|
||||
@@ -41,8 +41,7 @@ namespace OpenRA.GameRules
|
||||
|
||||
public void Load(IReadOnlyFileSystem fileSystem)
|
||||
{
|
||||
Stream stream;
|
||||
if (!fileSystem.TryOpen(Filename, out stream))
|
||||
if (!fileSystem.TryOpen(Filename, out var stream))
|
||||
return;
|
||||
|
||||
try
|
||||
@@ -50,8 +49,7 @@ namespace OpenRA.GameRules
|
||||
Exists = true;
|
||||
foreach (var loader in Game.ModData.SoundLoaders)
|
||||
{
|
||||
ISoundFormat soundFormat;
|
||||
if (loader.TryParseSound(stream, out soundFormat))
|
||||
if (loader.TryParseSound(stream, out var soundFormat))
|
||||
{
|
||||
Length = (int)soundFormat.LengthInSeconds;
|
||||
soundFormat.Dispose();
|
||||
|
||||
@@ -208,7 +208,7 @@ namespace OpenRA
|
||||
|
||||
// TODO: Top-level dictionary should be moved into the Ruleset instead of in its own object
|
||||
var sequences = mapSequences == null ? modData.DefaultSequences[tileSet] :
|
||||
new SequenceProvider(fileSystem, modData, ts, mapSequences);
|
||||
new SequenceProvider(fileSystem, modData, tileSet, mapSequences);
|
||||
|
||||
var modelSequences = dr.ModelSequences;
|
||||
if (mapModelSequences != null)
|
||||
|
||||
@@ -24,8 +24,8 @@ namespace OpenRA.GameRules
|
||||
public int[] DamageModifiers;
|
||||
public int[] InaccuracyModifiers;
|
||||
public int[] RangeModifiers;
|
||||
public int Facing;
|
||||
public Func<int> CurrentMuzzleFacing;
|
||||
public WAngle Facing;
|
||||
public Func<WAngle> CurrentMuzzleFacing;
|
||||
public WPos Source;
|
||||
public Func<WPos> CurrentSource;
|
||||
public Actor SourceActor;
|
||||
@@ -38,6 +38,8 @@ namespace OpenRA.GameRules
|
||||
public WeaponInfo Weapon;
|
||||
public int[] DamageModifiers = { };
|
||||
public WPos? Source;
|
||||
public WRot ImpactOrientation;
|
||||
public WPos ImpactPosition;
|
||||
public Actor SourceActor;
|
||||
public Target WeaponTarget;
|
||||
|
||||
@@ -45,11 +47,22 @@ namespace OpenRA.GameRules
|
||||
{
|
||||
Weapon = args.Weapon;
|
||||
DamageModifiers = args.DamageModifiers;
|
||||
ImpactPosition = args.PassiveTarget;
|
||||
Source = args.Source;
|
||||
SourceActor = args.SourceActor;
|
||||
WeaponTarget = args.GuidedTarget;
|
||||
}
|
||||
|
||||
// For places that only want to update some of the fields (usually DamageModifiers)
|
||||
public WarheadArgs(WarheadArgs args)
|
||||
{
|
||||
Weapon = args.Weapon;
|
||||
DamageModifiers = args.DamageModifiers;
|
||||
Source = args.Source;
|
||||
SourceActor = args.SourceActor;
|
||||
WeaponTarget = args.WeaponTarget;
|
||||
}
|
||||
|
||||
// Default empty constructor for callers that want to initialize fields themselves
|
||||
public WarheadArgs() { }
|
||||
}
|
||||
@@ -92,6 +105,12 @@ namespace OpenRA.GameRules
|
||||
[Desc("What types of targets are unaffected.", "Overrules ValidTargets.")]
|
||||
public readonly BitSet<TargetableType> InvalidTargets;
|
||||
|
||||
static readonly BitSet<TargetableType> TargetTypeAir = new BitSet<TargetableType>("Air");
|
||||
|
||||
[Desc("If weapon is not directly targeting an actor and targeted position is above this altitude,",
|
||||
"the weapon will ignore terrain target types and only check TargetTypeAir for validity.")]
|
||||
public readonly WDist AirThreshold = new WDist(128);
|
||||
|
||||
[Desc("Delay in ticks between firing shots from the same ammo magazine. If one entry, it will be used for all bursts.",
|
||||
"If multiple entries, their number needs to match Burst - 1.")]
|
||||
public readonly int[] BurstDelays = { 5 };
|
||||
@@ -118,8 +137,7 @@ namespace OpenRA.GameRules
|
||||
|
||||
static object LoadProjectile(MiniYaml yaml)
|
||||
{
|
||||
MiniYaml proj;
|
||||
if (!yaml.ToDictionary().TryGetValue("Projectile", out proj))
|
||||
if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj))
|
||||
return null;
|
||||
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
|
||||
FieldLoader.Load(ret, proj);
|
||||
@@ -145,7 +163,7 @@ namespace OpenRA.GameRules
|
||||
}
|
||||
|
||||
/// <summary>Checks if the weapon is valid against (can target) the target.</summary>
|
||||
public bool IsValidAgainst(Target target, World world, Actor firedBy)
|
||||
public bool IsValidAgainst(in Target target, World world, Actor firedBy)
|
||||
{
|
||||
if (target.Type == TargetType.Actor)
|
||||
return IsValidAgainst(target.Actor, firedBy);
|
||||
@@ -155,6 +173,10 @@ namespace OpenRA.GameRules
|
||||
|
||||
if (target.Type == TargetType.Terrain)
|
||||
{
|
||||
var dat = world.Map.DistanceAboveTerrain(target.CenterPosition);
|
||||
if (dat > AirThreshold)
|
||||
return IsValidTarget(TargetTypeAir);
|
||||
|
||||
var cell = world.Map.CellContaining(target.CenterPosition);
|
||||
if (!world.Map.Contains(cell))
|
||||
return false;
|
||||
@@ -198,20 +220,24 @@ namespace OpenRA.GameRules
|
||||
}
|
||||
|
||||
/// <summary>Applies all the weapon's warheads to the target.</summary>
|
||||
public void Impact(Target target, WarheadArgs args)
|
||||
public void Impact(in Target target, WarheadArgs args)
|
||||
{
|
||||
var world = args.SourceActor.World;
|
||||
foreach (var warhead in Warheads)
|
||||
{
|
||||
if (warhead.Delay > 0)
|
||||
world.AddFrameEndTask(w => w.Add(new DelayedImpact(warhead.Delay, warhead, target, args)));
|
||||
{
|
||||
// Lambdas can't use 'in' variables, so capture a copy for later
|
||||
var delayedTarget = target;
|
||||
world.AddFrameEndTask(w => w.Add(new DelayedImpact(warhead.Delay, warhead, delayedTarget, args)));
|
||||
}
|
||||
else
|
||||
warhead.DoImpact(target, args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Applies all the weapon's warheads to the target. Only use for projectile-less, special-case impacts.</summary>
|
||||
public void Impact(Target target, Actor firedBy)
|
||||
public void Impact(in Target target, Actor firedBy)
|
||||
{
|
||||
// The impact will happen immediately at target.CenterPosition.
|
||||
var args = new WarheadArgs
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace OpenRA.Graphics
|
||||
public bool IsDecoration { get; set; }
|
||||
|
||||
readonly SequenceProvider sequenceProvider;
|
||||
readonly Func<int> facingFunc;
|
||||
readonly Func<WAngle> facingFunc;
|
||||
readonly Func<bool> paused;
|
||||
|
||||
int frame;
|
||||
@@ -33,15 +33,15 @@ namespace OpenRA.Graphics
|
||||
Action tickFunc = () => { };
|
||||
|
||||
public Animation(World world, string name)
|
||||
: this(world, name, () => 0) { }
|
||||
: this(world, name, () => WAngle.Zero) { }
|
||||
|
||||
public Animation(World world, string name, Func<int> facingFunc)
|
||||
public Animation(World world, string name, Func<WAngle> facingFunc)
|
||||
: this(world, name, facingFunc, null) { }
|
||||
|
||||
public Animation(World world, string name, Func<bool> paused)
|
||||
: this(world, name, () => 0, paused) { }
|
||||
: this(world, name, () => WAngle.Zero, paused) { }
|
||||
|
||||
public Animation(World world, string name, Func<int> facingFunc, Func<bool> paused)
|
||||
public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
|
||||
{
|
||||
sequenceProvider = world.Map.Rules.Sequences;
|
||||
Name = name.ToLowerInvariant();
|
||||
@@ -54,12 +54,12 @@ namespace OpenRA.Graphics
|
||||
|
||||
public IRenderable[] Render(WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale)
|
||||
{
|
||||
var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, scale, IsDecoration);
|
||||
var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, scale, IsDecoration, CurrentSequence.IgnoreWorldTint);
|
||||
|
||||
if (CurrentSequence.ShadowStart >= 0)
|
||||
{
|
||||
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
||||
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, scale, true);
|
||||
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, scale, true, CurrentSequence.IgnoreWorldTint);
|
||||
return new IRenderable[] { shadowRenderable, imageRenderable };
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
frame = CurrentSequence.Length - 1;
|
||||
tickFunc = () => { };
|
||||
if (after != null) after();
|
||||
after?.Invoke();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -54,10 +54,10 @@ namespace OpenRA.Graphics
|
||||
|
||||
public static IReadOnlyDictionary<string, Collection> Collections { get; private set; }
|
||||
static Dictionary<string, Collection> collections;
|
||||
static Dictionary<string, Pair<Sheet, int>> cachedSheets;
|
||||
static Dictionary<string, (Sheet Sheet, int Density)> cachedSheets;
|
||||
static Dictionary<string, Dictionary<string, Sprite>> cachedSprites;
|
||||
static Dictionary<string, Sprite[]> cachedPanelSprites;
|
||||
static Dictionary<Collection, Pair<Sheet, int>> cachedCollectionSheets;
|
||||
static Dictionary<Collection, (Sheet Sheet, int)> cachedCollectionSheets;
|
||||
|
||||
static IReadOnlyFileSystem fileSystem;
|
||||
static float dpiScale = 1;
|
||||
@@ -72,10 +72,10 @@ namespace OpenRA.Graphics
|
||||
|
||||
fileSystem = modData.DefaultFileSystem;
|
||||
collections = new Dictionary<string, Collection>();
|
||||
cachedSheets = new Dictionary<string, Pair<Sheet, int>>();
|
||||
cachedSheets = new Dictionary<string, (Sheet, int)>();
|
||||
cachedSprites = new Dictionary<string, Dictionary<string, Sprite>>();
|
||||
cachedPanelSprites = new Dictionary<string, Sprite[]>();
|
||||
cachedCollectionSheets = new Dictionary<Collection, Pair<Sheet, int>>();
|
||||
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
|
||||
|
||||
Collections = new ReadOnlyDictionary<string, Collection>(collections);
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
if (cachedSheets != null)
|
||||
foreach (var sheet in cachedSheets.Values)
|
||||
sheet.First.Dispose();
|
||||
sheet.Sheet.Dispose();
|
||||
|
||||
collections = null;
|
||||
cachedSheets = null;
|
||||
@@ -108,12 +108,10 @@ namespace OpenRA.Graphics
|
||||
collections.Add(name, FieldLoader.Load<Collection>(yaml));
|
||||
}
|
||||
|
||||
static Pair<Sheet, int> SheetForCollection(Collection c)
|
||||
static (Sheet Sheet, int Density) SheetForCollection(Collection c)
|
||||
{
|
||||
Pair<Sheet, int> sheetDensity;
|
||||
|
||||
// Outer cache avoids recalculating image names
|
||||
if (!cachedCollectionSheets.TryGetValue(c, out sheetDensity))
|
||||
if (!cachedCollectionSheets.TryGetValue(c, out (Sheet, int) sheetDensity))
|
||||
{
|
||||
var image = c.Image;
|
||||
var density = 1;
|
||||
@@ -137,7 +135,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
|
||||
|
||||
sheetDensity = Pair.New(sheet, density);
|
||||
sheetDensity = (sheet, density);
|
||||
cachedSheets.Add(image, sheetDensity);
|
||||
}
|
||||
|
||||
@@ -153,20 +151,16 @@ namespace OpenRA.Graphics
|
||||
return null;
|
||||
|
||||
// Cached sprite
|
||||
Dictionary<string, Sprite> cachedCollection;
|
||||
Sprite sprite;
|
||||
if (cachedSprites.TryGetValue(collectionName, out cachedCollection) && cachedCollection.TryGetValue(imageName, out sprite))
|
||||
if (cachedSprites.TryGetValue(collectionName, out var cachedCollection) && cachedCollection.TryGetValue(imageName, out var sprite))
|
||||
return sprite;
|
||||
|
||||
Collection collection;
|
||||
if (!collections.TryGetValue(collectionName, out collection))
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return null;
|
||||
}
|
||||
|
||||
Rectangle mi;
|
||||
if (!collection.Regions.TryGetValue(imageName, out mi))
|
||||
if (!collection.Regions.TryGetValue(imageName, out var mi))
|
||||
return null;
|
||||
|
||||
// Cache the sprite
|
||||
@@ -177,7 +171,7 @@ namespace OpenRA.Graphics
|
||||
cachedSprites.Add(collectionName, cachedCollection);
|
||||
}
|
||||
|
||||
var image = new Sprite(sheetDensity.First, sheetDensity.Second * mi, TextureChannel.RGBA, 1f / sheetDensity.Second);
|
||||
var image = new Sprite(sheetDensity.Sheet, sheetDensity.Density * mi, TextureChannel.RGBA, 1f / sheetDensity.Density);
|
||||
cachedCollection.Add(imageName, image);
|
||||
|
||||
return image;
|
||||
@@ -189,12 +183,10 @@ namespace OpenRA.Graphics
|
||||
return null;
|
||||
|
||||
// Cached sprite
|
||||
Sprite[] cachedSprites;
|
||||
if (cachedPanelSprites.TryGetValue(collectionName, out cachedSprites))
|
||||
if (cachedPanelSprites.TryGetValue(collectionName, out var cachedSprites))
|
||||
return cachedSprites;
|
||||
|
||||
Collection collection;
|
||||
if (!collections.TryGetValue(collectionName, out collection))
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return null;
|
||||
@@ -214,20 +206,20 @@ namespace OpenRA.Graphics
|
||||
var pr = collection.PanelRegion;
|
||||
var ps = collection.PanelSides;
|
||||
|
||||
var sides = new[]
|
||||
var sides = new (PanelSides PanelSides, Rectangle Bounds)[]
|
||||
{
|
||||
Pair.New(PanelSides.Top | PanelSides.Left, new Rectangle(pr[0], pr[1], pr[2], pr[3])),
|
||||
Pair.New(PanelSides.Top, new Rectangle(pr[0] + pr[2], pr[1], pr[4], pr[3])),
|
||||
Pair.New(PanelSides.Top | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1], pr[6], pr[3])),
|
||||
Pair.New(PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3], pr[2], pr[5])),
|
||||
Pair.New(PanelSides.Center, new Rectangle(pr[0] + pr[2], pr[1] + pr[3], pr[4], pr[5])),
|
||||
Pair.New(PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3], pr[6], pr[5])),
|
||||
Pair.New(PanelSides.Bottom | PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3] + pr[5], pr[2], pr[7])),
|
||||
Pair.New(PanelSides.Bottom, new Rectangle(pr[0] + pr[2], pr[1] + pr[3] + pr[5], pr[4], pr[7])),
|
||||
Pair.New(PanelSides.Bottom | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3] + pr[5], pr[6], pr[7]))
|
||||
(PanelSides.Top | PanelSides.Left, new Rectangle(pr[0], pr[1], pr[2], pr[3])),
|
||||
(PanelSides.Top, new Rectangle(pr[0] + pr[2], pr[1], pr[4], pr[3])),
|
||||
(PanelSides.Top | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1], pr[6], pr[3])),
|
||||
(PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3], pr[2], pr[5])),
|
||||
(PanelSides.Center, new Rectangle(pr[0] + pr[2], pr[1] + pr[3], pr[4], pr[5])),
|
||||
(PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3], pr[6], pr[5])),
|
||||
(PanelSides.Bottom | PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3] + pr[5], pr[2], pr[7])),
|
||||
(PanelSides.Bottom, new Rectangle(pr[0] + pr[2], pr[1] + pr[3] + pr[5], pr[4], pr[7])),
|
||||
(PanelSides.Bottom | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3] + pr[5], pr[6], pr[7]))
|
||||
};
|
||||
|
||||
sprites = sides.Select(x => ps.HasSide(x.First) ? new Sprite(sheetDensity.First, sheetDensity.Second * x.Second, TextureChannel.RGBA, 1f / sheetDensity.Second) : null)
|
||||
sprites = sides.Select(x => ps.HasSide(x.PanelSides) ? new Sprite(sheetDensity.Sheet, sheetDensity.Density * x.Bounds, TextureChannel.RGBA, 1f / sheetDensity.Density) : null)
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
@@ -256,8 +248,7 @@ namespace OpenRA.Graphics
|
||||
if (string.IsNullOrEmpty(collectionName))
|
||||
return new Size(0, 0);
|
||||
|
||||
Collection collection;
|
||||
if (!collections.TryGetValue(collectionName, out collection))
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return new Size(0, 0);
|
||||
|
||||
@@ -236,15 +236,14 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
for (var i = 0; i < width; i++)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(palette[frame.Data[j * width + i]]);
|
||||
var c = palette[frame.Data[j * width + i]];
|
||||
var rgba = palette[frame.Data[j * width + i]];
|
||||
var k = 4 * (j * width + i);
|
||||
|
||||
// Convert RGBA to BGRA
|
||||
data[k] = bytes[2];
|
||||
data[k + 1] = bytes[1];
|
||||
data[k + 2] = bytes[0];
|
||||
data[k + 3] = bytes[3];
|
||||
data[k] = (byte)(rgba >> 16);
|
||||
data[k + 1] = (byte)(rgba >> 8);
|
||||
data[k + 2] = (byte)(rgba >> 0);
|
||||
data[k + 3] = (byte)(rgba >> 24);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,10 @@ namespace OpenRA.Graphics
|
||||
Palette = palette;
|
||||
Name = name;
|
||||
|
||||
Frames = cache[cursorSrc].Skip(Start).ToArray();
|
||||
|
||||
if ((d.ContainsKey("Length") && d["Length"].Value == "*") || (d.ContainsKey("End") && d["End"].Value == "*"))
|
||||
Length = Frames.Length - Start;
|
||||
Length = Frames.Length;
|
||||
else if (d.ContainsKey("Length"))
|
||||
Length = Exts.ParseIntegerInvariant(d["Length"].Value);
|
||||
else if (d.ContainsKey("End"))
|
||||
@@ -40,22 +42,17 @@ namespace OpenRA.Graphics
|
||||
else
|
||||
Length = 1;
|
||||
|
||||
Frames = cache[cursorSrc]
|
||||
.Skip(Start)
|
||||
.Take(Length)
|
||||
.ToArray();
|
||||
Frames = Frames.Take(Length).ToArray();
|
||||
|
||||
if (d.ContainsKey("X"))
|
||||
{
|
||||
int x;
|
||||
Exts.TryParseIntegerInvariant(d["X"].Value, out x);
|
||||
Exts.TryParseIntegerInvariant(d["X"].Value, out var x);
|
||||
Hotspot = Hotspot.WithX(x);
|
||||
}
|
||||
|
||||
if (d.ContainsKey("Y"))
|
||||
{
|
||||
int y;
|
||||
Exts.TryParseIntegerInvariant(d["Y"].Value, out y);
|
||||
Exts.TryParseIntegerInvariant(d["Y"].Value, out var y);
|
||||
Hotspot = Hotspot.WithY(y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,19 +38,16 @@ namespace OpenRA.Graphics
|
||||
|
||||
public IPalette GetPalette(string name)
|
||||
{
|
||||
MutablePalette mutable;
|
||||
if (modifiablePalettes.TryGetValue(name, out mutable))
|
||||
if (modifiablePalettes.TryGetValue(name, out var mutable))
|
||||
return mutable.AsReadOnly();
|
||||
ImmutablePalette immutable;
|
||||
if (palettes.TryGetValue(name, out immutable))
|
||||
if (palettes.TryGetValue(name, out var immutable))
|
||||
return immutable;
|
||||
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
|
||||
}
|
||||
|
||||
public int GetPaletteIndex(string name)
|
||||
{
|
||||
int ret;
|
||||
if (!indices.TryGetValue(name, out ret))
|
||||
if (!indices.TryGetValue(name, out var ret))
|
||||
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
@@ -19,12 +18,12 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public readonly IModel Model;
|
||||
public readonly Func<WVec> OffsetFunc;
|
||||
public readonly Func<IEnumerable<WRot>> RotationFunc;
|
||||
public readonly Func<WRot> RotationFunc;
|
||||
public readonly Func<bool> DisableFunc;
|
||||
public readonly Func<uint> FrameFunc;
|
||||
public readonly bool ShowShadow;
|
||||
|
||||
public ModelAnimation(IModel model, Func<WVec> offset, Func<IEnumerable<WRot>> rotation, Func<bool> disable, Func<uint> frame, bool showshadow)
|
||||
public ModelAnimation(IModel model, Func<WVec> offset, Func<WRot> rotation, Func<bool> disable, Func<uint> frame, bool showshadow)
|
||||
{
|
||||
Model = model;
|
||||
OffsetFunc = offset;
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
readonly Dictionary<Sheet, IFrameBuffer> mappedBuffers = new Dictionary<Sheet, IFrameBuffer>();
|
||||
readonly Stack<KeyValuePair<Sheet, IFrameBuffer>> unmappedBuffers = new Stack<KeyValuePair<Sheet, IFrameBuffer>>();
|
||||
readonly List<Pair<Sheet, Action>> doRender = new List<Pair<Sheet, Action>>();
|
||||
readonly List<(Sheet Sheet, Action Func)> doRender = new List<(Sheet, Action)>();
|
||||
|
||||
SheetBuilder sheetBuilderForFrame;
|
||||
bool isInFrame;
|
||||
@@ -79,8 +79,8 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
public ModelRenderProxy RenderAsync(
|
||||
WorldRenderer wr, IEnumerable<ModelAnimation> models, WRot camera, float scale,
|
||||
float[] groundNormal, WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
|
||||
WorldRenderer wr, IEnumerable<ModelAnimation> models, in WRot camera, float scale,
|
||||
float[] groundNormal, in WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
|
||||
PaletteReference color, PaletteReference normals, PaletteReference shadowPalette)
|
||||
{
|
||||
if (!isInFrame)
|
||||
@@ -114,8 +114,7 @@ namespace OpenRA.Graphics
|
||||
var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(m.OffsetFunc()));
|
||||
var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);
|
||||
|
||||
var worldTransform = m.RotationFunc().Aggregate(Util.IdentityMatrix(),
|
||||
(x, y) => Util.MatrixMultiply(Util.MakeFloatMatrix(y.AsMatrix()), x));
|
||||
var worldTransform = Util.MakeFloatMatrix(m.RotationFunc().AsMatrix());
|
||||
worldTransform = Util.MatrixMultiply(scaleTransform, worldTransform);
|
||||
worldTransform = Util.MatrixMultiply(offsetTransform, worldTransform);
|
||||
|
||||
@@ -161,10 +160,8 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
// Shadows are rendered at twice the resolution to reduce artifacts
|
||||
Size spriteSize, shadowSpriteSize;
|
||||
int2 spriteOffset, shadowSpriteOffset;
|
||||
CalculateSpriteGeometry(tl, br, 1, out spriteSize, out spriteOffset);
|
||||
CalculateSpriteGeometry(stl, sbr, 2, out shadowSpriteSize, out shadowSpriteOffset);
|
||||
CalculateSpriteGeometry(tl, br, 1, out var spriteSize, out var spriteOffset);
|
||||
CalculateSpriteGeometry(stl, sbr, 2, out var shadowSpriteSize, out var shadowSpriteOffset);
|
||||
|
||||
if (sheetBuilderForFrame == null)
|
||||
sheetBuilderForFrame = new SheetBuilder(SheetType.BGRA, AllocateSheet);
|
||||
@@ -181,7 +178,7 @@ namespace OpenRA.Graphics
|
||||
var correctionTransform = Util.MatrixMultiply(translateMtx, FlipMtx);
|
||||
var shadowCorrectionTransform = Util.MatrixMultiply(shadowTranslateMtx, ShadowScaleFlipMtx);
|
||||
|
||||
doRender.Add(Pair.New<Sheet, Action>(sprite.Sheet, () =>
|
||||
doRender.Add((sprite.Sheet, () =>
|
||||
{
|
||||
foreach (var m in models)
|
||||
{
|
||||
@@ -189,8 +186,7 @@ namespace OpenRA.Graphics
|
||||
var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(m.OffsetFunc()));
|
||||
var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);
|
||||
|
||||
var rotations = m.RotationFunc().Aggregate(Util.IdentityMatrix(),
|
||||
(x, y) => Util.MatrixMultiply(Util.MakeFloatMatrix(y.AsMatrix()), x));
|
||||
var rotations = Util.MakeFloatMatrix(m.RotationFunc().AsMatrix());
|
||||
var worldTransform = Util.MatrixMultiply(scaleTransform, rotations);
|
||||
worldTransform = Util.MatrixMultiply(offsetTransform, worldTransform);
|
||||
|
||||
@@ -326,16 +322,16 @@ namespace OpenRA.Graphics
|
||||
foreach (var v in doRender)
|
||||
{
|
||||
// Change sheet
|
||||
if (v.First != currentSheet)
|
||||
if (v.Sheet != currentSheet)
|
||||
{
|
||||
if (fbo != null)
|
||||
DisableFrameBuffer(fbo);
|
||||
|
||||
currentSheet = v.First;
|
||||
currentSheet = v.Sheet;
|
||||
fbo = EnableFrameBuffer(currentSheet);
|
||||
}
|
||||
|
||||
v.Second();
|
||||
v.Func();
|
||||
}
|
||||
|
||||
if (fbo != null)
|
||||
|
||||
@@ -15,9 +15,18 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public enum GLProfile
|
||||
{
|
||||
Automatic,
|
||||
ANGLE,
|
||||
Modern,
|
||||
Embedded,
|
||||
Legacy
|
||||
}
|
||||
|
||||
public interface IPlatform
|
||||
{
|
||||
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay);
|
||||
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile, bool enableLegacyGL);
|
||||
ISoundEngine CreateSound(string device);
|
||||
IFont CreateFont(byte[] data);
|
||||
}
|
||||
@@ -32,7 +41,10 @@ namespace OpenRA
|
||||
Subtractive,
|
||||
Multiply,
|
||||
Multiplicative,
|
||||
DoubleMultiplicative
|
||||
DoubleMultiplicative,
|
||||
LowAdditive,
|
||||
Screen,
|
||||
Translucent
|
||||
}
|
||||
|
||||
public interface IPlatformWindow : IDisposable
|
||||
@@ -46,6 +58,7 @@ namespace OpenRA
|
||||
Size SurfaceSize { get; }
|
||||
int DisplayCount { get; }
|
||||
int CurrentDisplay { get; }
|
||||
bool HasInputFocus { get; }
|
||||
|
||||
event Action<float, float, float, float> OnWindowScaleChanged;
|
||||
|
||||
@@ -60,6 +73,10 @@ namespace OpenRA
|
||||
void SetHardwareCursor(IHardwareCursor cursor);
|
||||
void SetRelativeMouseMode(bool mode);
|
||||
void SetScaleModifier(float scale);
|
||||
|
||||
GLProfile GLProfile { get; }
|
||||
|
||||
GLProfile[] SupportedGLProfiles { get; }
|
||||
}
|
||||
|
||||
public interface IGraphicsContext : IDisposable
|
||||
@@ -86,8 +103,7 @@ namespace OpenRA
|
||||
{
|
||||
void Bind();
|
||||
void SetData(T[] vertices, int length);
|
||||
void SetData(T[] vertices, int start, int length);
|
||||
void SetData(IntPtr data, int start, int length);
|
||||
void SetData(T[] vertices, int offset, int start, int length);
|
||||
}
|
||||
|
||||
public interface IShader
|
||||
|
||||
@@ -47,14 +47,13 @@ namespace OpenRA.Graphics
|
||||
remapRamp = ramp.Select(r => r - ramp[rampMaxIndex]);
|
||||
}
|
||||
|
||||
remapColors = remapRamp.Select((x, i) => Pair.New(baseIndex + i, Exts.ColorLerp(x / (float)ramp.Length, c1, c2)))
|
||||
.ToDictionary(u => u.First, u => u.Second);
|
||||
remapColors = remapRamp.Select((x, i) => (baseIndex + i, Exts.ColorLerp(x / (float)ramp.Length, c1, c2)))
|
||||
.ToDictionary(u => u.Item1, u => u.Item2);
|
||||
}
|
||||
|
||||
public Color GetRemappedColor(Color original, int index)
|
||||
{
|
||||
Color c;
|
||||
return remapColors.TryGetValue(index, out c)
|
||||
return remapColors.TryGetValue(index, out var c)
|
||||
? c : original;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,11 @@ namespace OpenRA.Graphics
|
||||
IFinalizedRenderable PrepareRender(WorldRenderer wr);
|
||||
}
|
||||
|
||||
public interface ITintableRenderable
|
||||
{
|
||||
IRenderable WithTint(in float3 newTint);
|
||||
}
|
||||
|
||||
public interface IFinalizedRenderable
|
||||
{
|
||||
void Render(WorldRenderer wr);
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace OpenRA.Graphics
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void DrawLine(float3 start, float3 end, float width, Color startColor, Color endColor)
|
||||
public void DrawLine(in float3 start, in float3 end, float width, Color startColor, Color endColor)
|
||||
{
|
||||
var delta = (end - start) / (end - start).XY.Length;
|
||||
var corner = width / 2 * new float3(-delta.Y, delta.X, delta.Z);
|
||||
@@ -55,7 +55,7 @@ namespace OpenRA.Graphics
|
||||
parent.DrawRGBAVertices(vertices);
|
||||
}
|
||||
|
||||
public void DrawLine(float3 start, float3 end, float width, Color color)
|
||||
public void DrawLine(in float3 start, in float3 end, float width, Color color)
|
||||
{
|
||||
var delta = (end - start) / (end - start).XY.Length;
|
||||
var corner = width / 2 * new float2(-delta.Y, delta.X);
|
||||
@@ -80,7 +80,7 @@ namespace OpenRA.Graphics
|
||||
/// Will behave badly if the lines are parallel.
|
||||
/// Z position is the average of a and b (ignores actual intersection point if it exists)
|
||||
/// </summary>
|
||||
float3 IntersectionOf(float3 a, float3 da, float3 b, float3 db)
|
||||
float3 IntersectionOf(in float3 a, in float3 da, in float3 b, in float3 db)
|
||||
{
|
||||
var crossA = a.X * (a.Y + da.Y) - a.Y * (a.X + da.X);
|
||||
var crossB = b.X * (b.Y + db.Y) - b.Y * (b.X + db.X);
|
||||
@@ -193,14 +193,14 @@ namespace OpenRA.Graphics
|
||||
DrawConnectedLine(vertices.Select(v => new float3(v, 0)).ToArray(), width, color, true);
|
||||
}
|
||||
|
||||
public void DrawRect(float3 tl, float3 br, float width, Color color)
|
||||
public void DrawRect(in float3 tl, in float3 br, float width, Color color)
|
||||
{
|
||||
var tr = new float3(br.X, tl.Y, tl.Z);
|
||||
var bl = new float3(tl.X, br.Y, br.Z);
|
||||
DrawPolygon(new[] { tl, tr, br, bl }, width, color);
|
||||
}
|
||||
|
||||
public void FillTriangle(float3 a, float3 b, float3 c, Color color)
|
||||
public void FillTriangle(in float3 a, in float3 b, in float3 c, Color color)
|
||||
{
|
||||
color = Util.PremultiplyAlpha(color);
|
||||
var cr = color.R / 255.0f;
|
||||
@@ -214,14 +214,14 @@ namespace OpenRA.Graphics
|
||||
parent.DrawRGBAVertices(vertices);
|
||||
}
|
||||
|
||||
public void FillRect(float3 tl, float3 br, Color color)
|
||||
public void FillRect(in float3 tl, in float3 br, Color color)
|
||||
{
|
||||
var tr = new float3(br.X, tl.Y, tl.Z);
|
||||
var bl = new float3(tl.X, br.Y, br.Z);
|
||||
FillRect(tl, tr, br, bl, color);
|
||||
}
|
||||
|
||||
public void FillRect(float3 a, float3 b, float3 c, float3 d, Color color)
|
||||
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color color)
|
||||
{
|
||||
color = Util.PremultiplyAlpha(color);
|
||||
var cr = color.R / 255.0f;
|
||||
@@ -238,7 +238,7 @@ namespace OpenRA.Graphics
|
||||
parent.DrawRGBAVertices(vertices);
|
||||
}
|
||||
|
||||
public void FillRect(float3 a, float3 b, float3 c, float3 d, Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor)
|
||||
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor)
|
||||
{
|
||||
vertices[0] = VertexWithColor(a + Offset, topLeftColor);
|
||||
vertices[1] = VertexWithColor(b + Offset, topRightColor);
|
||||
@@ -250,7 +250,7 @@ namespace OpenRA.Graphics
|
||||
parent.DrawRGBAVertices(vertices);
|
||||
}
|
||||
|
||||
static Vertex VertexWithColor(float3 xyz, Color color)
|
||||
static Vertex VertexWithColor(in float3 xyz, Color color)
|
||||
{
|
||||
color = Util.PremultiplyAlpha(color);
|
||||
var cr = color.R / 255.0f;
|
||||
@@ -261,7 +261,7 @@ namespace OpenRA.Graphics
|
||||
return new Vertex(xyz, cr, cg, cb, ca, 0, 0);
|
||||
}
|
||||
|
||||
public void FillEllipse(float3 tl, float3 br, Color color, int vertices = 32)
|
||||
public void FillEllipse(in float3 tl, in float3 br, Color color, int vertices = 32)
|
||||
{
|
||||
// TODO: Create an ellipse polygon instead
|
||||
var a = (br.X - tl.X) / 2;
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace OpenRA.Graphics
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float3 location, float3 size)
|
||||
public void DrawSprite(Sprite s, in float3 location, in float3 size)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
|
||||
@@ -30,7 +30,7 @@ namespace OpenRA.Graphics
|
||||
parent.DrawSprite(s, location, 0, size);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float3 location)
|
||||
public void DrawSprite(Sprite s, in float3 location)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
|
||||
@@ -38,12 +38,28 @@ namespace OpenRA.Graphics
|
||||
parent.DrawSprite(s, location, 0, s.Size);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float3 a, float3 b, float3 c, float3 d)
|
||||
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
|
||||
|
||||
parent.DrawSprite(s, a, b, c, d);
|
||||
}
|
||||
|
||||
public void DrawSpriteWithTint(Sprite s, in float3 location, in float3 size, in float3 tint)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
|
||||
|
||||
parent.DrawSpriteWithTint(s, location, 0, size, tint);
|
||||
}
|
||||
|
||||
public void DrawSpriteWithTint(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
|
||||
|
||||
parent.DrawSpriteWithTint(s, a, b, c, d, tint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,29 +32,29 @@ namespace OpenRA.Graphics
|
||||
int ShadowZOffset { get; }
|
||||
int[] Frames { get; }
|
||||
Rectangle Bounds { get; }
|
||||
bool IgnoreWorldTint { get; }
|
||||
|
||||
Sprite GetSprite(int frame);
|
||||
Sprite GetSprite(int frame, int facing);
|
||||
Sprite GetShadow(int frame, int facing);
|
||||
Sprite GetSprite(int frame, WAngle facing);
|
||||
Sprite GetShadow(int frame, WAngle facing);
|
||||
}
|
||||
|
||||
public interface ISpriteSequenceLoader
|
||||
{
|
||||
Action<string> OnMissingSpriteError { get; set; }
|
||||
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, TileSet tileSet, SpriteCache cache, MiniYamlNode node);
|
||||
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node);
|
||||
}
|
||||
|
||||
public class SequenceProvider : IDisposable
|
||||
{
|
||||
readonly ModData modData;
|
||||
readonly TileSet tileSet;
|
||||
readonly string tileSet;
|
||||
readonly Lazy<Sequences> sequences;
|
||||
readonly Lazy<SpriteCache> spriteCache;
|
||||
public SpriteCache SpriteCache { get { return spriteCache.Value; } }
|
||||
|
||||
readonly Dictionary<string, UnitSequences> sequenceCache = new Dictionary<string, UnitSequences>();
|
||||
|
||||
public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, TileSet tileSet, MiniYaml additionalSequences)
|
||||
public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, string tileSet, MiniYaml additionalSequences)
|
||||
{
|
||||
this.modData = modData;
|
||||
this.tileSet = tileSet;
|
||||
@@ -69,17 +69,17 @@ namespace OpenRA.Graphics
|
||||
|
||||
public ISpriteSequence GetSequence(string unitName, string sequenceName)
|
||||
{
|
||||
UnitSequences unitSeq;
|
||||
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
|
||||
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
|
||||
|
||||
ISpriteSequence seq;
|
||||
if (!unitSeq.Value.TryGetValue(sequenceName, out seq))
|
||||
if (!unitSeq.Value.TryGetValue(sequenceName, out var seq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have a sequence named `{1}`".F(unitName, sequenceName));
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
public IEnumerable<string> Images { get { return sequences.Value.Keys; } }
|
||||
|
||||
public bool HasSequence(string unitName)
|
||||
{
|
||||
return sequences.Value.ContainsKey(unitName);
|
||||
@@ -87,8 +87,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public bool HasSequence(string unitName, string sequenceName)
|
||||
{
|
||||
UnitSequences unitSeq;
|
||||
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
|
||||
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
|
||||
|
||||
return unitSeq.Value.ContainsKey(sequenceName);
|
||||
@@ -96,8 +95,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public IEnumerable<string> Sequences(string unitName)
|
||||
{
|
||||
UnitSequences unitSeq;
|
||||
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
|
||||
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
|
||||
|
||||
return unitSeq.Value.Keys;
|
||||
@@ -107,15 +105,15 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
var nodes = MiniYaml.Load(fileSystem, modData.Manifest.Sequences, additionalSequences);
|
||||
var items = new Dictionary<string, UnitSequences>();
|
||||
foreach (var n in nodes)
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
// Work around the loop closure issue in older versions of C#
|
||||
var node = n;
|
||||
// Nodes starting with ^ are inheritable but never loaded directly
|
||||
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
var key = node.Value.ToLines(node.Key).JoinWith("|");
|
||||
|
||||
UnitSequences t;
|
||||
if (sequenceCache.TryGetValue(key, out t))
|
||||
if (sequenceCache.TryGetValue(key, out var t))
|
||||
items.Add(node.Key, t);
|
||||
else
|
||||
{
|
||||
|
||||
@@ -146,8 +146,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (texture != null)
|
||||
texture.Dispose();
|
||||
texture?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Size, 0, frame.Offset); }
|
||||
public Sprite Add(byte[] src, Size size) { return Add(src, size, 0, float3.Zero); }
|
||||
public Sprite Add(byte[] src, Size size, float zRamp, float3 spriteOffset)
|
||||
public Sprite Add(byte[] src, Size size, float zRamp, in float3 spriteOffset)
|
||||
{
|
||||
// Don't bother allocating empty sprites
|
||||
if (size.Width == 0 || size.Height == 0)
|
||||
@@ -115,7 +115,7 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
public Sprite Allocate(Size imageSize, float scale = 1f) { return Allocate(imageSize, 0, float3.Zero, scale); }
|
||||
public Sprite Allocate(Size imageSize, float zRamp, float3 spriteOffset, float scale = 1f)
|
||||
public Sprite Allocate(Size imageSize, float zRamp, in float3 spriteOffset, float scale = 1f)
|
||||
{
|
||||
if (imageSize.Width + p.X + margin > current.Size.Width)
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace OpenRA.Graphics
|
||||
public Sprite(Sheet sheet, Rectangle bounds, TextureChannel channel, float scale = 1)
|
||||
: this(sheet, bounds, 0, float2.Zero, channel, BlendMode.Alpha, scale) { }
|
||||
|
||||
public Sprite(Sheet sheet, Rectangle bounds, float zRamp, float3 offset, TextureChannel channel, BlendMode blendMode = BlendMode.Alpha, float scale = 1f)
|
||||
public Sprite(Sheet sheet, Rectangle bounds, float zRamp, in float3 offset, TextureChannel channel, BlendMode blendMode = BlendMode.Alpha, float scale = 1f)
|
||||
{
|
||||
Sheet = sheet;
|
||||
Bounds = bounds;
|
||||
|
||||
@@ -23,8 +23,8 @@ namespace OpenRA.Graphics
|
||||
readonly SheetBuilder builder;
|
||||
readonly Func<string, float> lineWidth;
|
||||
readonly IFont font;
|
||||
readonly Cache<Pair<char, Color>, GlyphInfo> glyphs;
|
||||
readonly Cache<Tuple<char, Color, int>, Sprite> contrastGlyphs;
|
||||
readonly Cache<char, GlyphInfo> glyphs;
|
||||
readonly Cache<(char C, int Radius), Sprite> contrastGlyphs;
|
||||
readonly Cache<int, float[]> dilationElements;
|
||||
|
||||
float deviceScale;
|
||||
@@ -39,17 +39,20 @@ namespace OpenRA.Graphics
|
||||
this.builder = builder;
|
||||
|
||||
font = Game.Renderer.CreateFont(data);
|
||||
|
||||
glyphs = new Cache<Pair<char, Color>, GlyphInfo>(CreateGlyph, Pair<char, Color>.EqualityComparer);
|
||||
contrastGlyphs = new Cache<Tuple<char, Color, int>, Sprite>(CreateContrastGlyph);
|
||||
glyphs = new Cache<char, GlyphInfo>(CreateGlyph);
|
||||
contrastGlyphs = new Cache<(char, int), Sprite>(CreateContrastGlyph);
|
||||
dilationElements = new Cache<int, float[]>(CreateCircularWeightMap);
|
||||
|
||||
// PERF: Cache these delegates for Measure calls.
|
||||
Func<char, float> characterWidth = character => glyphs[Pair.New(character, Color.White)].Advance;
|
||||
Func<char, float> characterWidth = character => glyphs[character].Advance;
|
||||
lineWidth = line => line.Sum(characterWidth) / deviceScale;
|
||||
|
||||
// Pre-cache small font sizes so glyphs are immediately available when we need them
|
||||
if (size <= 24)
|
||||
PrecacheColor(Color.White, name);
|
||||
using (new PerfTimer("Precache {0} {1}px".F(name, size)))
|
||||
for (var n = (char)0x20; n < (char)0x7f; n++)
|
||||
if (glyphs[n] == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
TopOffset = size - ascender;
|
||||
}
|
||||
@@ -61,14 +64,6 @@ namespace OpenRA.Graphics
|
||||
contrastGlyphs.Clear();
|
||||
}
|
||||
|
||||
void PrecacheColor(Color c, string name)
|
||||
{
|
||||
using (new PerfTimer("PrecacheColor {0} {1}px {2}".F(name, size, c)))
|
||||
for (var n = (char)0x20; n < (char)0x7f; n++)
|
||||
if (glyphs[Pair.New(n, c)] == null)
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
void DrawTextContrast(string text, float2 location, Color contrastColor, int contrastOffset)
|
||||
{
|
||||
// Offset from the baseline position to the top-left of the glyph for rendering
|
||||
@@ -78,6 +73,7 @@ namespace OpenRA.Graphics
|
||||
var screenContrast = (int)(contrastOffset * deviceScale);
|
||||
var screen = new int2((int)(location.X * deviceScale + 0.5f), (int)(location.Y * deviceScale + 0.5f));
|
||||
var contrastVector = new float2(screenContrast, screenContrast);
|
||||
var tint = new float3(contrastColor.R / 255f, contrastColor.G / 255f, contrastColor.B / 255f);
|
||||
foreach (var s in text)
|
||||
{
|
||||
if (s == '\n')
|
||||
@@ -87,15 +83,16 @@ namespace OpenRA.Graphics
|
||||
continue;
|
||||
}
|
||||
|
||||
var g = glyphs[Pair.New(s, Color.Black)];
|
||||
var g = glyphs[s];
|
||||
|
||||
// Convert screen coordinates back to UI coordinates for drawing
|
||||
if (g.Sprite != null)
|
||||
{
|
||||
var contrastSprite = contrastGlyphs[Tuple.Create(s, contrastColor, screenContrast)];
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSprite(contrastSprite,
|
||||
var contrastSprite = contrastGlyphs[(s, screenContrast)];
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(contrastSprite,
|
||||
(screen + g.Offset - contrastVector) / deviceScale,
|
||||
contrastSprite.Size / deviceScale);
|
||||
contrastSprite.Size / deviceScale,
|
||||
tint);
|
||||
}
|
||||
|
||||
screen += new int2((int)(g.Advance + 0.5f), 0);
|
||||
@@ -109,6 +106,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
// Calculate positions in screen pixel coordinates
|
||||
var screen = new int2((int)(location.X * deviceScale + 0.5f), (int)(location.Y * deviceScale + 0.5f));
|
||||
var tint = new float3(c.R / 255f, c.G / 255f, c.B / 255f);
|
||||
foreach (var s in text)
|
||||
{
|
||||
if (s == '\n')
|
||||
@@ -118,13 +116,14 @@ namespace OpenRA.Graphics
|
||||
continue;
|
||||
}
|
||||
|
||||
var g = glyphs[Pair.New(s, c)];
|
||||
var g = glyphs[s];
|
||||
|
||||
// Convert screen coordinates back to UI coordinates for drawing
|
||||
if (g.Sprite != null)
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSprite(g.Sprite,
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(g.Sprite,
|
||||
(screen + g.Offset).ToFloat2() / deviceScale,
|
||||
g.Sprite.Size / deviceScale);
|
||||
g.Sprite.Size / deviceScale,
|
||||
tint);
|
||||
|
||||
screen += new int2((int)(g.Advance + 0.5f), 0);
|
||||
}
|
||||
@@ -144,6 +143,7 @@ namespace OpenRA.Graphics
|
||||
var offset = new float2(0, size);
|
||||
var cosa = (float)Math.Cos(-angle);
|
||||
var sina = (float)Math.Sin(-angle);
|
||||
var tint = new float3(c.R / 255f, c.G / 255f, c.B / 255f);
|
||||
|
||||
var p = offset;
|
||||
foreach (var s in text)
|
||||
@@ -155,7 +155,7 @@ namespace OpenRA.Graphics
|
||||
continue;
|
||||
}
|
||||
|
||||
var g = glyphs[Pair.New(s, c)];
|
||||
var g = glyphs[s];
|
||||
if (g.Sprite != null)
|
||||
{
|
||||
var tl = new float2(
|
||||
@@ -172,11 +172,12 @@ namespace OpenRA.Graphics
|
||||
|
||||
// Offset rotated glyph to align the top-left corner with the screen pixel grid
|
||||
var screenOffset = new float2((int)(ra.X * deviceScale + 0.5f), (int)(ra.Y * deviceScale + 0.5f)) / deviceScale - ra;
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSprite(g.Sprite,
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(g.Sprite,
|
||||
ra + screenOffset,
|
||||
rb + screenOffset,
|
||||
rc + screenOffset,
|
||||
rd + screenOffset);
|
||||
rd + screenOffset,
|
||||
tint);
|
||||
}
|
||||
|
||||
p += new float2(g.Advance / deviceScale, 0);
|
||||
@@ -238,13 +239,20 @@ namespace OpenRA.Graphics
|
||||
return new int2(0, size);
|
||||
|
||||
var lines = text.Split('\n');
|
||||
return new int2((int)Math.Ceiling(lines.Max(lineWidth)), lines.Length * size);
|
||||
return new int2((int)Math.Ceiling(MaxLineWidth(lines, lineWidth)), lines.Length * size);
|
||||
}
|
||||
|
||||
GlyphInfo CreateGlyph(Pair<char, Color> c)
|
||||
static float MaxLineWidth(string[] lines, Func<string, float> lineWidth)
|
||||
{
|
||||
var glyph = font.CreateGlyph(c.First, size, deviceScale);
|
||||
var maxWidth = 0f;
|
||||
foreach (var line in lines)
|
||||
maxWidth = Math.Max(maxWidth, lineWidth(line));
|
||||
return maxWidth;
|
||||
}
|
||||
|
||||
GlyphInfo CreateGlyph(char c)
|
||||
{
|
||||
var glyph = font.CreateGlyph(c, size, deviceScale);
|
||||
if (glyph.Data == null)
|
||||
{
|
||||
return new GlyphInfo
|
||||
@@ -274,12 +282,10 @@ namespace OpenRA.Graphics
|
||||
if (p != 0)
|
||||
{
|
||||
var q = destStride * (j + s.Bounds.Top) + 4 * (i + s.Bounds.Left);
|
||||
var pmc = Util.PremultiplyAlpha(Color.FromArgb(p, c.Second));
|
||||
|
||||
dest[q] = pmc.B;
|
||||
dest[q + 1] = pmc.G;
|
||||
dest[q + 2] = pmc.R;
|
||||
dest[q + 3] = pmc.A;
|
||||
dest[q] = p;
|
||||
dest[q + 1] = p;
|
||||
dest[q + 2] = p;
|
||||
dest[q + 3] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -347,16 +353,12 @@ namespace OpenRA.Graphics
|
||||
return elem;
|
||||
}
|
||||
|
||||
Sprite CreateContrastGlyph(Tuple<char, Color, int> c)
|
||||
Sprite CreateContrastGlyph((char C, int Radius) c)
|
||||
{
|
||||
// Source glyph color doesn't matter, so use black
|
||||
var glyph = glyphs[Pair.New(c.Item1, Color.Black)];
|
||||
var color = c.Item2;
|
||||
var r = c.Item3;
|
||||
var glyph = glyphs[c.C];
|
||||
var r = c.Radius;
|
||||
|
||||
var size = new Size(glyph.Sprite.Bounds.Width + 2 * r, glyph.Sprite.Bounds.Height + 2 * r);
|
||||
|
||||
var s = builder.Allocate(size);
|
||||
var s = builder.Allocate(new Size(glyph.Sprite.Bounds.Width + 2 * r, glyph.Sprite.Bounds.Height + 2 * r));
|
||||
var dest = s.Sheet.GetData();
|
||||
var destStride = s.Sheet.Size.Width * 4;
|
||||
|
||||
@@ -398,11 +400,10 @@ namespace OpenRA.Graphics
|
||||
if (alpha > 0)
|
||||
{
|
||||
var q = destStride * (j + s.Bounds.Top) + 4 * (i + s.Bounds.Left);
|
||||
var pmc = Util.PremultiplyAlpha(Color.FromArgb(alpha, color));
|
||||
dest[q] = pmc.B;
|
||||
dest[q + 1] = pmc.G;
|
||||
dest[q + 2] = pmc.R;
|
||||
dest[q + 3] = pmc.A;
|
||||
dest[q] = alpha;
|
||||
dest[q + 1] = alpha;
|
||||
dest[q + 2] = alpha;
|
||||
dest[q + 3] = alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,8 +76,7 @@ namespace OpenRA.Graphics
|
||||
var allSprites = sprites.GetOrAdd(filename);
|
||||
var sprite = allSprites.FirstOrDefault();
|
||||
|
||||
ISpriteFrame[] unloaded;
|
||||
if (!unloadedFrames.TryGetValue(filename, out unloaded))
|
||||
if (!unloadedFrames.TryGetValue(filename, out var unloaded))
|
||||
unloaded = null;
|
||||
|
||||
// This is the first time that the file has been requested
|
||||
@@ -85,8 +84,7 @@ namespace OpenRA.Graphics
|
||||
// the loaded cache (initially empty)
|
||||
if (sprite == null)
|
||||
{
|
||||
TypeDictionary fileMetadata = null;
|
||||
unloaded = FrameLoader.GetFrames(fileSystem, filename, loaders, out fileMetadata);
|
||||
unloaded = FrameLoader.GetFrames(fileSystem, filename, loaders, out var fileMetadata);
|
||||
unloadedFrames[filename] = unloaded;
|
||||
metadata[filename] = fileMetadata;
|
||||
|
||||
@@ -125,8 +123,7 @@ namespace OpenRA.Graphics
|
||||
/// </summary>
|
||||
public TypeDictionary FrameMetadata(string filename)
|
||||
{
|
||||
TypeDictionary fileMetadata;
|
||||
if (!metadata.TryGetValue(filename, out fileMetadata))
|
||||
if (!metadata.TryGetValue(filename, out var fileMetadata))
|
||||
{
|
||||
FrameLoader.GetFrames(fileSystem, filename, loaders, out fileMetadata);
|
||||
metadata[filename] = fileMetadata;
|
||||
@@ -142,8 +139,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public FrameCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders)
|
||||
{
|
||||
TypeDictionary metadata;
|
||||
frames = new Cache<string, ISpriteFrame[]>(filename => FrameLoader.GetFrames(fileSystem, filename, loaders, out metadata));
|
||||
frames = new Cache<string, ISpriteFrame[]>(filename => FrameLoader.GetFrames(fileSystem, filename, loaders, out _));
|
||||
}
|
||||
|
||||
public ISpriteFrame[] this[string filename] { get { return frames[filename]; } }
|
||||
@@ -165,11 +161,10 @@ namespace OpenRA.Graphics
|
||||
|
||||
public static ISpriteFrame[] GetFrames(Stream stream, ISpriteLoader[] loaders, out TypeDictionary metadata)
|
||||
{
|
||||
ISpriteFrame[] frames;
|
||||
metadata = null;
|
||||
|
||||
foreach (var loader in loaders)
|
||||
if (loader.TryParseSprite(stream, out frames, out metadata))
|
||||
if (loader.TryParseSprite(stream, out var frames, out metadata))
|
||||
return frames;
|
||||
|
||||
return null;
|
||||
|
||||
@@ -14,7 +14,7 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public struct SpriteRenderable : IRenderable, IFinalizedRenderable
|
||||
public struct SpriteRenderable : IRenderable, ITintableRenderable, IFinalizedRenderable
|
||||
{
|
||||
public static readonly IEnumerable<IRenderable> None = new IRenderable[0];
|
||||
|
||||
@@ -24,9 +24,17 @@ namespace OpenRA.Graphics
|
||||
readonly int zOffset;
|
||||
readonly PaletteReference palette;
|
||||
readonly float scale;
|
||||
readonly float3 tint;
|
||||
readonly bool isDecoration;
|
||||
readonly bool ignoreWorldTint;
|
||||
|
||||
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration)
|
||||
: this(sprite, pos, offset, zOffset, palette, scale, float3.Ones, isDecoration, false) { }
|
||||
|
||||
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration, bool ignoreWorldTint)
|
||||
: this(sprite, pos, offset, zOffset, palette, scale, float3.Ones, isDecoration, ignoreWorldTint) { }
|
||||
|
||||
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float3 tint, bool isDecoration, bool ignoreWorldTint)
|
||||
{
|
||||
this.sprite = sprite;
|
||||
this.pos = pos;
|
||||
@@ -34,7 +42,9 @@ namespace OpenRA.Graphics
|
||||
this.zOffset = zOffset;
|
||||
this.palette = palette;
|
||||
this.scale = scale;
|
||||
this.tint = tint;
|
||||
this.isDecoration = isDecoration;
|
||||
this.ignoreWorldTint = ignoreWorldTint;
|
||||
}
|
||||
|
||||
public WPos Pos { get { return pos + offset; } }
|
||||
@@ -43,10 +53,12 @@ namespace OpenRA.Graphics
|
||||
public int ZOffset { get { return zOffset; } }
|
||||
public bool IsDecoration { get { return isDecoration; } }
|
||||
|
||||
public IRenderable WithPalette(PaletteReference newPalette) { return new SpriteRenderable(sprite, pos, offset, zOffset, newPalette, scale, isDecoration); }
|
||||
public IRenderable WithZOffset(int newOffset) { return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, isDecoration); }
|
||||
public IRenderable OffsetBy(WVec vec) { return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, isDecoration); }
|
||||
public IRenderable AsDecoration() { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, true); }
|
||||
public IRenderable WithPalette(PaletteReference newPalette) { return new SpriteRenderable(sprite, pos, offset, zOffset, newPalette, scale, tint, isDecoration, ignoreWorldTint); }
|
||||
public IRenderable WithZOffset(int newOffset) { return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, tint, isDecoration, ignoreWorldTint); }
|
||||
public IRenderable OffsetBy(WVec vec) { return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, tint, isDecoration, ignoreWorldTint); }
|
||||
public IRenderable AsDecoration() { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, tint, true, ignoreWorldTint); }
|
||||
|
||||
public IRenderable WithTint(in float3 newTint) { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, newTint, isDecoration, ignoreWorldTint); }
|
||||
|
||||
float3 ScreenPosition(WorldRenderer wr)
|
||||
{
|
||||
@@ -59,7 +71,17 @@ namespace OpenRA.Graphics
|
||||
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
|
||||
public void Render(WorldRenderer wr)
|
||||
{
|
||||
Game.Renderer.WorldSpriteRenderer.DrawSprite(sprite, ScreenPosition(wr), palette, scale * sprite.Size);
|
||||
var wsr = Game.Renderer.WorldSpriteRenderer;
|
||||
if (ignoreWorldTint)
|
||||
wsr.DrawSprite(sprite, ScreenPosition(wr), palette, scale * sprite.Size);
|
||||
else
|
||||
{
|
||||
var t = tint;
|
||||
if (wr.TerrainLighting != null)
|
||||
t *= wr.TerrainLighting.TintAt(pos);
|
||||
|
||||
wsr.DrawSpriteWithTint(sprite, ScreenPosition(wr), palette, scale * sprite.Size, t);
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr)
|
||||
|
||||
@@ -10,17 +10,21 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class SpriteRenderer : Renderer.IBatchRenderer
|
||||
{
|
||||
const int SheetCount = 7;
|
||||
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => "Texture{0}".F(i));
|
||||
|
||||
readonly Renderer renderer;
|
||||
readonly IShader shader;
|
||||
|
||||
readonly Vertex[] vertices;
|
||||
readonly Sheet[] sheets = new Sheet[7];
|
||||
readonly Sheet[] sheets = new Sheet[SheetCount];
|
||||
|
||||
BlendMode currentBlend = BlendMode.Alpha;
|
||||
int nv = 0;
|
||||
@@ -39,7 +43,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
for (var i = 0; i < ns; i++)
|
||||
{
|
||||
shader.SetTexture("Texture{0}".F(i), sheets[i].GetTexture());
|
||||
shader.SetTexture(SheetIndexToTextureName[i], sheets[i].GetTexture());
|
||||
sheets[i] = null;
|
||||
}
|
||||
|
||||
@@ -104,27 +108,46 @@ namespace OpenRA.Graphics
|
||||
return new int2(sheetIndex, secondarySheetIndex);
|
||||
}
|
||||
|
||||
internal void DrawSprite(Sprite s, float3 location, float paletteTextureIndex, float3 size)
|
||||
internal void DrawSprite(Sprite s, in float3 location, float paletteTextureIndex, in float3 size)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size);
|
||||
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size, float3.Ones);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float3 location, PaletteReference pal)
|
||||
public void DrawSprite(Sprite s, in float3 location, PaletteReference pal)
|
||||
{
|
||||
DrawSprite(s, location, pal.TextureIndex, s.Size);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float3 location, PaletteReference pal, float3 size)
|
||||
public void DrawSprite(Sprite s, in float3 location, PaletteReference pal, float3 size)
|
||||
{
|
||||
DrawSprite(s, location, pal.TextureIndex, size);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float3 a, float3 b, float3 c, float3 d)
|
||||
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, nv);
|
||||
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, float3.Ones, nv);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
internal void DrawSpriteWithTint(Sprite s, in float3 location, float paletteTextureIndex, in float3 size, in float3 tint)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size, tint);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
public void DrawSpriteWithTint(Sprite s, in float3 location, PaletteReference pal, in float3 size, in float3 tint)
|
||||
{
|
||||
DrawSpriteWithTint(s, location, pal.TextureIndex, size, tint);
|
||||
}
|
||||
|
||||
public void DrawSpriteWithTint(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, tint, nv);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class TerrainSpriteLayer : IDisposable
|
||||
{
|
||||
static readonly int[] CornerVertexMap = { 0, 1, 2, 2, 3, 0 };
|
||||
|
||||
public readonly Sheet Sheet;
|
||||
public readonly BlendMode BlendMode;
|
||||
|
||||
@@ -25,6 +27,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
readonly IVertexBuffer<Vertex> vertexBuffer;
|
||||
readonly Vertex[] vertices;
|
||||
readonly bool[] ignoreTint;
|
||||
readonly HashSet<int> dirtyRows = new HashSet<int>();
|
||||
readonly int rowStride;
|
||||
readonly bool restrictToBounds;
|
||||
@@ -50,6 +53,12 @@ namespace OpenRA.Graphics
|
||||
emptySprite = new Sprite(sheet, Rectangle.Empty, TextureChannel.Alpha);
|
||||
|
||||
wr.PaletteInvalidated += UpdatePaletteIndices;
|
||||
|
||||
if (wr.TerrainLighting != null)
|
||||
{
|
||||
ignoreTint = new bool[rowStride * map.MapSize.Y];
|
||||
wr.TerrainLighting.CellChanged += UpdateTint;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdatePaletteIndices()
|
||||
@@ -59,22 +68,76 @@ namespace OpenRA.Graphics
|
||||
for (var i = 0; i < vertices.Length; i++)
|
||||
{
|
||||
var v = vertices[i];
|
||||
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C);
|
||||
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, v.R, v.G, v.B);
|
||||
}
|
||||
|
||||
for (var row = 0; row < map.MapSize.Y; row++)
|
||||
dirtyRows.Add(row);
|
||||
}
|
||||
|
||||
public void Update(CPos cell, Sprite sprite)
|
||||
public void Clear(CPos cell)
|
||||
{
|
||||
var xyz = sprite == null ? float3.Zero :
|
||||
worldRenderer.Screen3DPosition(map.CenterOfCell(cell)) + sprite.Offset - 0.5f * sprite.Size;
|
||||
|
||||
Update(cell.ToMPos(map.Grid.Type), sprite, xyz);
|
||||
Update(cell, null, true);
|
||||
}
|
||||
|
||||
public void Update(MPos uv, Sprite sprite, float3 pos)
|
||||
public void Update(CPos cell, ISpriteSequence sequence, int frame)
|
||||
{
|
||||
Update(cell, sequence.GetSprite(frame), sequence.IgnoreWorldTint);
|
||||
}
|
||||
|
||||
public void Update(CPos cell, Sprite sprite, bool ignoreTint)
|
||||
{
|
||||
var xyz = float3.Zero;
|
||||
if (sprite != null)
|
||||
{
|
||||
var cellOrigin = map.CenterOfCell(cell) - new WVec(0, 0, map.Grid.Ramps[map.Ramp[cell]].CenterHeightOffset);
|
||||
xyz = worldRenderer.Screen3DPosition(cellOrigin) + sprite.Offset - 0.5f * sprite.Size;
|
||||
}
|
||||
|
||||
Update(cell.ToMPos(map.Grid.Type), sprite, xyz, ignoreTint);
|
||||
}
|
||||
|
||||
void UpdateTint(MPos uv)
|
||||
{
|
||||
var offset = rowStride * uv.V + 6 * uv.U;
|
||||
if (ignoreTint[offset])
|
||||
{
|
||||
var noTint = float3.Ones;
|
||||
for (var i = 0; i < 6; i++)
|
||||
{
|
||||
var v = vertices[offset + i];
|
||||
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, noTint);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow the terrain tint to vary linearly across the cell to smooth out the staircase effect
|
||||
// This is done by sampling the lighting the corners of the sprite, even though those pixels are
|
||||
// transparent for isometric tiles
|
||||
var tl = worldRenderer.TerrainLighting;
|
||||
var pos = map.CenterOfCell(uv.ToCPos(map));
|
||||
var step = map.Grid.Type == MapGridType.RectangularIsometric ? 724 : 512;
|
||||
var weights = new[]
|
||||
{
|
||||
tl.TintAt(pos + new WVec(-step, -step, 0)),
|
||||
tl.TintAt(pos + new WVec(step, -step, 0)),
|
||||
tl.TintAt(pos + new WVec(step, step, 0)),
|
||||
tl.TintAt(pos + new WVec(-step, step, 0))
|
||||
};
|
||||
|
||||
// Apply tint directly to the underlying vertices
|
||||
// This saves us from having to re-query the sprite information, which has not changed
|
||||
for (var i = 0; i < 6; i++)
|
||||
{
|
||||
var v = vertices[offset + i];
|
||||
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, weights[CornerVertexMap[i]]);
|
||||
}
|
||||
|
||||
dirtyRows.Add(uv.V);
|
||||
}
|
||||
|
||||
public void Update(MPos uv, Sprite sprite, in float3 pos, bool ignoreTint)
|
||||
{
|
||||
if (sprite != null)
|
||||
{
|
||||
@@ -92,7 +155,13 @@ namespace OpenRA.Graphics
|
||||
return;
|
||||
|
||||
var offset = rowStride * uv.V + 6 * uv.U;
|
||||
Util.FastCreateQuad(vertices, pos, sprite, int2.Zero, palette.TextureIndex, offset, sprite.Size);
|
||||
Util.FastCreateQuad(vertices, pos, sprite, int2.Zero, palette.TextureIndex, offset, sprite.Size, float3.Ones);
|
||||
|
||||
if (worldRenderer.TerrainLighting != null)
|
||||
{
|
||||
this.ignoreTint[offset] = ignoreTint;
|
||||
UpdateTint(uv);
|
||||
}
|
||||
|
||||
dirtyRows.Add(uv.V);
|
||||
}
|
||||
@@ -114,15 +183,7 @@ namespace OpenRA.Graphics
|
||||
continue;
|
||||
|
||||
var rowOffset = rowStride * row;
|
||||
|
||||
unsafe
|
||||
{
|
||||
// The compiler / language spec won't let us calculate a pointer to
|
||||
// an offset inside a generic array T[], and so we are forced to
|
||||
// calculate the start-of-row pointer here to pass in to SetData.
|
||||
fixed (Vertex* vPtr = &vertices[0])
|
||||
vertexBuffer.SetData((IntPtr)(vPtr + rowOffset), rowOffset, rowStride);
|
||||
}
|
||||
vertexBuffer.SetData(vertices, rowOffset, rowOffset, rowStride);
|
||||
}
|
||||
|
||||
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
|
||||
@@ -135,6 +196,9 @@ namespace OpenRA.Graphics
|
||||
public void Dispose()
|
||||
{
|
||||
worldRenderer.PaletteInvalidated -= UpdatePaletteIndices;
|
||||
if (worldRenderer.TerrainLighting != null)
|
||||
worldRenderer.TerrainLighting.CellChanged -= UpdateTint;
|
||||
|
||||
vertexBuffer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace OpenRA.Graphics
|
||||
readonly MersenneTwister random;
|
||||
TileSet tileset;
|
||||
|
||||
public Theater(TileSet tileset)
|
||||
public Theater(TileSet tileset, Action<uint, string> onMissingImage = null)
|
||||
{
|
||||
this.tileset = tileset;
|
||||
var allocated = false;
|
||||
@@ -63,9 +63,31 @@ namespace OpenRA.Graphics
|
||||
|
||||
foreach (var i in t.Value.Images)
|
||||
{
|
||||
var allFrames = frameCache[i];
|
||||
ISpriteFrame[] allFrames;
|
||||
if (onMissingImage != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
allFrames = frameCache[i];
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
onMissingImage(t.Key, i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
allFrames = frameCache[i];
|
||||
|
||||
var frameCount = tileset.EnableDepth ? allFrames.Length / 2 : allFrames.Length;
|
||||
var indices = t.Value.Frames != null ? t.Value.Frames : Enumerable.Range(0, frameCount);
|
||||
var indices = t.Value.Frames != null ? t.Value.Frames : Exts.MakeArray(t.Value.TilesCount, j => j);
|
||||
|
||||
var start = indices.Min();
|
||||
var end = indices.Max();
|
||||
if (start < 0 || end >= frameCount)
|
||||
throw new YamlException("Template `{0}` uses frames [{1}..{2}] of {3}, but only [0..{4}] actually exist"
|
||||
.F(t.Key, start, end, i, frameCount - 1));
|
||||
|
||||
variants.Add(indices.Select(j =>
|
||||
{
|
||||
var f = allFrames[j];
|
||||
@@ -82,7 +104,7 @@ namespace OpenRA.Graphics
|
||||
if (sheetBuilder == null)
|
||||
sheetBuilder = new SheetBuilder(SheetBuilder.FrameTypeToSheetType(f.Type), allocate);
|
||||
else if (type != sheetBuilder.Type)
|
||||
throw new InvalidDataException("Sprite type mismatch. Terrain sprites must all be either Indexed or RGBA.");
|
||||
throw new YamlException("Sprite type mismatch. Terrain sprites must all be either Indexed or RGBA.");
|
||||
|
||||
var s = sheetBuilder.Allocate(f.Size, zRamp, offset);
|
||||
Util.FastCopyIntoChannel(s, f.Data);
|
||||
@@ -107,6 +129,9 @@ namespace OpenRA.Graphics
|
||||
if (tileset.IgnoreTileSpriteOffsets)
|
||||
allSprites = allSprites.Select(s => new Sprite(s.Sheet, s.Bounds, s.ZRamp, new float3(float2.Zero, s.Offset.Z), s.Channel, s.BlendMode));
|
||||
|
||||
if (onMissingImage != null && !variants.Any())
|
||||
continue;
|
||||
|
||||
templates.Add(t.Value.Id, new TheaterTemplate(allSprites.ToArray(), variants.First().Count(), t.Value.Images.Length));
|
||||
}
|
||||
|
||||
@@ -116,10 +141,14 @@ namespace OpenRA.Graphics
|
||||
Sheet.ReleaseBuffer();
|
||||
}
|
||||
|
||||
public bool HasTileSprite(TerrainTile r, int? variant = null)
|
||||
{
|
||||
return TileSprite(r, variant) != missingTile;
|
||||
}
|
||||
|
||||
public Sprite TileSprite(TerrainTile r, int? variant = null)
|
||||
{
|
||||
TheaterTemplate template;
|
||||
if (!templates.TryGetValue(r.Type, out template))
|
||||
if (!templates.TryGetValue(r.Type, out var template))
|
||||
return missingTile;
|
||||
|
||||
if (r.Index >= template.Stride)
|
||||
|
||||
@@ -20,15 +20,18 @@ namespace OpenRA.Graphics
|
||||
// yes, our channel order is nuts.
|
||||
static readonly int[] ChannelMasks = { 2, 1, 0, 3 };
|
||||
|
||||
public static void FastCreateQuad(Vertex[] vertices, float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv, float3 size)
|
||||
public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv, in float3 size, in float3 tint)
|
||||
{
|
||||
var b = new float3(o.X + size.X, o.Y, o.Z);
|
||||
var c = new float3(o.X + size.X, o.Y + size.Y, o.Z + size.Z);
|
||||
var d = new float3(o.X, o.Y + size.Y, o.Z + size.Z);
|
||||
FastCreateQuad(vertices, o, b, c, d, r, samplers, paletteTextureIndex, nv);
|
||||
FastCreateQuad(vertices, o, b, c, d, r, samplers, paletteTextureIndex, tint, nv);
|
||||
}
|
||||
|
||||
public static void FastCreateQuad(Vertex[] vertices, float3 a, float3 b, float3 c, float3 d, Sprite r, int2 samplers, float paletteTextureIndex, int nv)
|
||||
public static void FastCreateQuad(Vertex[] vertices,
|
||||
in float3 a, in float3 b, in float3 c, in float3 d,
|
||||
Sprite r, int2 samplers, float paletteTextureIndex,
|
||||
in float3 tint, int nv)
|
||||
{
|
||||
float sl = 0;
|
||||
float st = 0;
|
||||
@@ -51,12 +54,12 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
var fAttribC = (float)attribC;
|
||||
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC);
|
||||
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC);
|
||||
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC);
|
||||
vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC);
|
||||
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC);
|
||||
vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC);
|
||||
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint);
|
||||
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC, tint);
|
||||
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint);
|
||||
vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint);
|
||||
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC, tint);
|
||||
vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint);
|
||||
}
|
||||
|
||||
public static void FastCopyIntoChannel(Sprite dest, byte[] src)
|
||||
|
||||
@@ -16,17 +16,34 @@ namespace OpenRA.Graphics
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Vertex
|
||||
{
|
||||
public readonly float X, Y, Z, S, T, U, V, P, C;
|
||||
// 3d position
|
||||
public readonly float X, Y, Z;
|
||||
|
||||
public Vertex(float3 xyz, float s, float t, float u, float v, float p, float c)
|
||||
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c) { }
|
||||
// Primary and secondary texture coordinates or RGBA color
|
||||
public readonly float S, T, U, V;
|
||||
|
||||
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c)
|
||||
// Palette and channel flags
|
||||
public readonly float P, C;
|
||||
|
||||
// Color tint
|
||||
public readonly float R, G, B;
|
||||
|
||||
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c)
|
||||
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, float3.Ones) { }
|
||||
|
||||
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c, in float3 tint)
|
||||
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z) { }
|
||||
|
||||
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, in float3 tint)
|
||||
: this(x, y, z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z) { }
|
||||
|
||||
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, float r, float g, float b)
|
||||
{
|
||||
X = x; Y = y; Z = z;
|
||||
S = s; T = t;
|
||||
U = u; V = v;
|
||||
P = p; C = c;
|
||||
R = r; G = g; B = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ namespace OpenRA.Graphics
|
||||
[Flags]
|
||||
public enum ScrollDirection { None = 0, Up = 1, Left = 2, Down = 4, Right = 8 }
|
||||
|
||||
public interface INotifyViewportZoomExtentsChanged
|
||||
{
|
||||
void ViewportZoomExtentsChanged(float minZoom, float maxZoom);
|
||||
}
|
||||
|
||||
public static class ViewportExts
|
||||
{
|
||||
public static bool Includes(this ScrollDirection d, ScrollDirection s)
|
||||
@@ -84,12 +89,22 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public float MinZoom { get { return minZoom; } }
|
||||
|
||||
public void AdjustZoom(float dz)
|
||||
{
|
||||
// Exponential ensures that equal positive and negative steps have the same effect
|
||||
Zoom = (zoom * (float)Math.Exp(dz)).Clamp(unlockMinZoom ? unlockedMinZoom : minZoom, maxZoom);
|
||||
}
|
||||
|
||||
public void AdjustZoom(float dz, int2 center)
|
||||
{
|
||||
var oldCenter = worldRenderer.Viewport.ViewToWorldPx(center);
|
||||
AdjustZoom(dz);
|
||||
var newCenter = worldRenderer.Viewport.ViewToWorldPx(center);
|
||||
CenterLocation += oldCenter - newCenter;
|
||||
}
|
||||
|
||||
public void ToggleZoom()
|
||||
{
|
||||
// Unlocked zooms always reset to the default zoom
|
||||
@@ -237,6 +252,9 @@ namespace OpenRA.Graphics
|
||||
Zoom = minZoom;
|
||||
else
|
||||
Zoom = Zoom.Clamp(minZoom, maxZoom);
|
||||
|
||||
foreach (var t in worldRenderer.World.WorldActor.TraitsImplementing<INotifyViewportZoomExtentsChanged>())
|
||||
t.ViewportZoomExtentsChanged(minZoom, maxZoom);
|
||||
}
|
||||
|
||||
public CPos ViewToWorld(int2 view)
|
||||
@@ -244,7 +262,6 @@ namespace OpenRA.Graphics
|
||||
var world = worldRenderer.Viewport.ViewToWorldPx(view);
|
||||
var map = worldRenderer.World.Map;
|
||||
var candidates = CandidateMouseoverCells(world).ToList();
|
||||
var tileSet = worldRenderer.World.Map.Rules.TileSet;
|
||||
|
||||
foreach (var uv in candidates)
|
||||
{
|
||||
@@ -253,18 +270,9 @@ namespace OpenRA.Graphics
|
||||
var s = worldRenderer.ScreenPxPosition(p);
|
||||
if (Math.Abs(s.X - world.X) <= tileSize.Width && Math.Abs(s.Y - world.Y) <= tileSize.Height)
|
||||
{
|
||||
var ramp = 0;
|
||||
if (map.Contains(uv))
|
||||
{
|
||||
var ti = tileSet.GetTileInfo(map.Tiles[uv]);
|
||||
if (ti != null)
|
||||
ramp = ti.RampType;
|
||||
}
|
||||
|
||||
var corners = map.Grid.CellCorners[ramp];
|
||||
var pos = map.CenterOfCell(uv.ToCPos(map));
|
||||
var screen = corners.Select(c => worldRenderer.ScreenPxPosition(pos + c)).ToArray();
|
||||
|
||||
var ramp = map.Grid.Ramps[map.Ramp.Contains(uv) ? map.Ramp[uv] : 0];
|
||||
var pos = map.CenterOfCell(uv.ToCPos(map)) - new WVec(0, 0, ramp.CenterHeightOffset);
|
||||
var screen = ramp.Corners.Select(c => worldRenderer.ScreenPxPosition(pos + c)).ToArray();
|
||||
if (screen.PolygonContains(world))
|
||||
return uv.ToCPos(map);
|
||||
}
|
||||
@@ -274,7 +282,7 @@ namespace OpenRA.Graphics
|
||||
// Try and find the closest cell
|
||||
if (candidates.Count > 0)
|
||||
{
|
||||
return candidates.OrderBy(uv =>
|
||||
return candidates.MinBy(uv =>
|
||||
{
|
||||
var p = map.CenterOfCell(uv.ToCPos(map.Grid.Type));
|
||||
var s = worldRenderer.ScreenPxPosition(p);
|
||||
@@ -282,7 +290,7 @@ namespace OpenRA.Graphics
|
||||
var dy = Math.Abs(s.Y - world.Y);
|
||||
|
||||
return dx * dx + dy * dy;
|
||||
}).First().ToCPos(map);
|
||||
}).ToCPos(map);
|
||||
}
|
||||
|
||||
// Something is very wrong, but lets return something that isn't completely bogus and hope the caller can recover
|
||||
@@ -306,7 +314,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public int2 ViewToWorldPx(int2 view) { return (graphicSettings.UIScale / Zoom * view.ToFloat2()).ToInt2() + TopLeft; }
|
||||
public int2 WorldToViewPx(int2 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).ToFloat2()).ToInt2(); }
|
||||
public int2 WorldToViewPx(float3 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).XY).ToInt2(); }
|
||||
public int2 WorldToViewPx(in float3 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).XY).ToInt2(); }
|
||||
|
||||
public void Center(IEnumerable<Actor> actors)
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class WorldRenderer : IDisposable
|
||||
{
|
||||
public static readonly Func<IRenderable, int> RenderableScreenZPositionComparisonKey =
|
||||
public static readonly Func<IRenderable, int> RenderableZPositionComparisonKey =
|
||||
r => ZPosition(r.Pos, r.ZOffset);
|
||||
|
||||
public readonly Size TileSize;
|
||||
@@ -28,6 +28,7 @@ namespace OpenRA.Graphics
|
||||
public readonly World World;
|
||||
public readonly Theater Theater;
|
||||
public Viewport Viewport { get; private set; }
|
||||
public readonly ITerrainLighting TerrainLighting;
|
||||
|
||||
public event Action PaletteInvalidated = null;
|
||||
|
||||
@@ -43,6 +44,8 @@ namespace OpenRA.Graphics
|
||||
readonly List<IFinalizedRenderable> preparedOverlayRenderables = new List<IFinalizedRenderable>();
|
||||
readonly List<IFinalizedRenderable> preparedAnnotationRenderables = new List<IFinalizedRenderable>();
|
||||
|
||||
readonly List<IRenderable> renderablesBuffer = new List<IRenderable>();
|
||||
|
||||
bool lastDepthPreviewEnabled;
|
||||
|
||||
internal WorldRenderer(ModData modData, World world)
|
||||
@@ -66,6 +69,7 @@ namespace OpenRA.Graphics
|
||||
palette.Initialize();
|
||||
|
||||
Theater = new Theater(world.Map.Rules.TileSet);
|
||||
TerrainLighting = world.WorldActor.TraitOrDefault<ITerrainLighting>();
|
||||
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
|
||||
|
||||
debugVis = Exts.Lazy(() => world.WorldActor.TraitOrDefault<DebugVisualizations>());
|
||||
@@ -93,8 +97,8 @@ namespace OpenRA.Graphics
|
||||
var oldHeight = palette.Height;
|
||||
palette.AddPalette(name, pal, allowModifiers);
|
||||
|
||||
if (oldHeight != palette.Height && PaletteInvalidated != null)
|
||||
PaletteInvalidated();
|
||||
if (oldHeight != palette.Height)
|
||||
PaletteInvalidated?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,78 +111,118 @@ namespace OpenRA.Graphics
|
||||
palettes[name].Palette = pal;
|
||||
}
|
||||
|
||||
IEnumerable<IFinalizedRenderable> GenerateRenderables()
|
||||
// PERF: Avoid LINQ.
|
||||
void GenerateRenderables()
|
||||
{
|
||||
var actors = onScreenActors.Append(World.WorldActor);
|
||||
if (World.RenderPlayer != null)
|
||||
actors = actors.Append(World.RenderPlayer.PlayerActor);
|
||||
foreach (var actor in onScreenActors)
|
||||
renderablesBuffer.AddRange(actor.Render(this));
|
||||
|
||||
renderablesBuffer.AddRange(World.WorldActor.Render(this));
|
||||
|
||||
if (World.RenderPlayer != null)
|
||||
renderablesBuffer.AddRange(World.RenderPlayer.PlayerActor.Render(this));
|
||||
|
||||
var worldRenderables = actors.SelectMany(a => a.Render(this));
|
||||
if (World.OrderGenerator != null)
|
||||
worldRenderables = worldRenderables.Concat(World.OrderGenerator.Render(this, World));
|
||||
renderablesBuffer.AddRange(World.OrderGenerator.Render(this, World));
|
||||
|
||||
// Unpartitioned effects
|
||||
worldRenderables = worldRenderables.Concat(World.UnpartitionedEffects.SelectMany(e => e.Render(this)));
|
||||
foreach (var e in World.UnpartitionedEffects)
|
||||
renderablesBuffer.AddRange(e.Render(this));
|
||||
|
||||
// Partitioned, currently on-screen effects
|
||||
var effectRenderables = World.ScreenMap.RenderableEffectsInBox(Viewport.TopLeft, Viewport.BottomRight);
|
||||
worldRenderables = worldRenderables.Concat(effectRenderables.SelectMany(e => e.Render(this)));
|
||||
foreach (var e in World.ScreenMap.RenderableEffectsInBox(Viewport.TopLeft, Viewport.BottomRight))
|
||||
renderablesBuffer.AddRange(e.Render(this));
|
||||
|
||||
worldRenderables = worldRenderables.OrderBy(RenderableScreenZPositionComparisonKey);
|
||||
// Renderables must be ordered using a stable sorting algorithm to avoid flickering artefacts
|
||||
foreach (var renderable in renderablesBuffer.OrderBy(RenderableZPositionComparisonKey))
|
||||
preparedRenderables.Add(renderable.PrepareRender(this));
|
||||
|
||||
return worldRenderables.Select(r => r.PrepareRender(this));
|
||||
// PERF: Reuse collection to avoid allocations.
|
||||
renderablesBuffer.Clear();
|
||||
}
|
||||
|
||||
IEnumerable<IFinalizedRenderable> GenerateOverlayRenderables()
|
||||
// PERF: Avoid LINQ.
|
||||
void GenerateOverlayRenderables()
|
||||
{
|
||||
var actors = World.ActorsWithTrait<IRenderAboveShroud>()
|
||||
.Where(a => a.Actor.IsInWorld && !a.Actor.Disposed && (!a.Trait.SpatiallyPartitionable || onScreenActors.Contains(a.Actor)))
|
||||
.SelectMany(a => a.Trait.RenderAboveShroud(a.Actor, this));
|
||||
foreach (var a in World.ActorsWithTrait<IRenderAboveShroud>())
|
||||
{
|
||||
if (!a.Actor.IsInWorld || a.Actor.Disposed || (a.Trait.SpatiallyPartitionable && !onScreenActors.Contains(a.Actor)))
|
||||
continue;
|
||||
|
||||
var selected = World.Selection.Actors.Where(a => a.IsInWorld && !a.Disposed)
|
||||
.SelectMany(a => a.TraitsImplementing<IRenderAboveShroudWhenSelected>()
|
||||
.Where(t => !t.SpatiallyPartitionable || onScreenActors.Contains(a))
|
||||
.SelectMany(t => t.RenderAboveShroud(a, this)));
|
||||
foreach (var renderable in a.Trait.RenderAboveShroud(a.Actor, this))
|
||||
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
|
||||
}
|
||||
|
||||
var effects = World.Effects.Select(e => e as IEffectAboveShroud)
|
||||
.Where(e => e != null)
|
||||
.SelectMany(e => e.RenderAboveShroud(this));
|
||||
foreach (var a in World.Selection.Actors)
|
||||
{
|
||||
if (!a.IsInWorld || a.Disposed)
|
||||
continue;
|
||||
|
||||
foreach (var t in a.TraitsImplementing<IRenderAboveShroudWhenSelected>())
|
||||
{
|
||||
if (t.SpatiallyPartitionable && !onScreenActors.Contains(a))
|
||||
continue;
|
||||
|
||||
foreach (var renderable in t.RenderAboveShroud(a, this))
|
||||
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var e in World.Effects)
|
||||
{
|
||||
var ea = e as IEffectAboveShroud;
|
||||
if (ea == null)
|
||||
continue;
|
||||
|
||||
foreach (var renderable in ea.RenderAboveShroud(this))
|
||||
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
|
||||
}
|
||||
|
||||
var orderGenerator = SpriteRenderable.None;
|
||||
if (World.OrderGenerator != null)
|
||||
orderGenerator = World.OrderGenerator.RenderAboveShroud(this, World);
|
||||
|
||||
return actors
|
||||
.Concat(selected)
|
||||
.Concat(effects)
|
||||
.Concat(orderGenerator)
|
||||
.Select(r => r.PrepareRender(this));
|
||||
foreach (var renderable in World.OrderGenerator.RenderAboveShroud(this, World))
|
||||
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
|
||||
}
|
||||
|
||||
IEnumerable<IFinalizedRenderable> GenerateAnnotationRenderables()
|
||||
// PERF: Avoid LINQ.
|
||||
void GenerateAnnotationRenderables()
|
||||
{
|
||||
var actors = World.ActorsWithTrait<IRenderAnnotations>()
|
||||
.Where(a => a.Actor.IsInWorld && !a.Actor.Disposed && (!a.Trait.SpatiallyPartitionable || onScreenActors.Contains(a.Actor)))
|
||||
.SelectMany(a => a.Trait.RenderAnnotations(a.Actor, this));
|
||||
foreach (var a in World.ActorsWithTrait<IRenderAnnotations>())
|
||||
{
|
||||
if (!a.Actor.IsInWorld || a.Actor.Disposed || (a.Trait.SpatiallyPartitionable && !onScreenActors.Contains(a.Actor)))
|
||||
continue;
|
||||
|
||||
var selected = World.Selection.Actors.Where(a => a.IsInWorld && !a.Disposed)
|
||||
.SelectMany(a => a.TraitsImplementing<IRenderAnnotationsWhenSelected>()
|
||||
.Where(t => !t.SpatiallyPartitionable || onScreenActors.Contains(a))
|
||||
.SelectMany(t => t.RenderAnnotations(a, this)));
|
||||
foreach (var renderAnnotation in a.Trait.RenderAnnotations(a.Actor, this))
|
||||
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
|
||||
}
|
||||
|
||||
var effects = World.Effects.Select(e => e as IEffectAnnotation)
|
||||
.Where(e => e != null)
|
||||
.SelectMany(e => e.RenderAnnotation(this));
|
||||
foreach (var a in World.Selection.Actors)
|
||||
{
|
||||
if (!a.IsInWorld || a.Disposed)
|
||||
continue;
|
||||
|
||||
foreach (var t in a.TraitsImplementing<IRenderAnnotationsWhenSelected>())
|
||||
{
|
||||
if (t.SpatiallyPartitionable && !onScreenActors.Contains(a))
|
||||
continue;
|
||||
|
||||
foreach (var renderAnnotation in t.RenderAnnotations(a, this))
|
||||
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var e in World.Effects)
|
||||
{
|
||||
var ea = e as IEffectAnnotation;
|
||||
if (ea == null)
|
||||
continue;
|
||||
|
||||
foreach (var renderAnnotation in ea.RenderAnnotation(this))
|
||||
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
|
||||
}
|
||||
|
||||
var orderGenerator = SpriteRenderable.None;
|
||||
if (World.OrderGenerator != null)
|
||||
orderGenerator = World.OrderGenerator.RenderAnnotations(this, World);
|
||||
|
||||
return actors
|
||||
.Concat(selected)
|
||||
.Concat(effects)
|
||||
.Concat(orderGenerator)
|
||||
.Select(r => r.PrepareRender(this));
|
||||
foreach (var renderAnnotation in World.OrderGenerator.RenderAnnotations(this, World))
|
||||
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
|
||||
}
|
||||
|
||||
public void PrepareRenderables()
|
||||
@@ -190,9 +234,11 @@ namespace OpenRA.Graphics
|
||||
|
||||
// PERF: Reuse collection to avoid allocations.
|
||||
onScreenActors.UnionWith(World.ScreenMap.RenderableActorsInBox(Viewport.TopLeft, Viewport.BottomRight));
|
||||
preparedRenderables.AddRange(GenerateRenderables());
|
||||
preparedOverlayRenderables.AddRange(GenerateOverlayRenderables());
|
||||
preparedAnnotationRenderables.AddRange(GenerateAnnotationRenderables());
|
||||
|
||||
GenerateRenderables();
|
||||
GenerateOverlayRenderables();
|
||||
GenerateAnnotationRenderables();
|
||||
|
||||
onScreenActors.Clear();
|
||||
}
|
||||
|
||||
@@ -213,8 +259,7 @@ namespace OpenRA.Graphics
|
||||
if (enableDepthBuffer)
|
||||
Game.Renderer.Context.EnableDepthBuffer();
|
||||
|
||||
if (terrainRenderer != null)
|
||||
terrainRenderer.RenderTerrain(this, Viewport);
|
||||
terrainRenderer?.RenderTerrain(this, Viewport);
|
||||
|
||||
Game.Renderer.Flush();
|
||||
|
||||
@@ -277,11 +322,16 @@ namespace OpenRA.Graphics
|
||||
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.MediumSpringGreen);
|
||||
}
|
||||
|
||||
foreach (var r in World.ScreenMap.MouseBounds(World.RenderPlayer))
|
||||
foreach (var b in World.ScreenMap.MouseBounds(World.RenderPlayer))
|
||||
{
|
||||
var tl = Viewport.WorldToViewPx(new float2(r.Left, r.Top));
|
||||
var br = Viewport.WorldToViewPx(new float2(r.Right, r.Bottom));
|
||||
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.OrangeRed);
|
||||
var points = new float2[b.Vertices.Length];
|
||||
for (var index = 0; index < b.Vertices.Length; index++)
|
||||
{
|
||||
var vertex = b.Vertices[index];
|
||||
points[index] = Viewport.WorldToViewPx(vertex).ToFloat2();
|
||||
}
|
||||
|
||||
Game.Renderer.RgbaColorRenderer.DrawPolygon(points, 1, Color.OrangeRed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,8 +50,7 @@ namespace OpenRA
|
||||
return () => keys[name];
|
||||
|
||||
// Try and parse as a hardcoded definition
|
||||
Hotkey key;
|
||||
if (!Hotkey.TryParse(name, out key))
|
||||
if (!Hotkey.TryParse(name, out var key))
|
||||
key = Hotkey.Invalid;
|
||||
|
||||
return () => key;
|
||||
@@ -59,8 +58,7 @@ namespace OpenRA
|
||||
|
||||
public void Set(string name, Hotkey value)
|
||||
{
|
||||
HotkeyDefinition definition;
|
||||
if (!definitions.TryGetValue(name, out definition))
|
||||
if (!definitions.TryGetValue(name, out var definition))
|
||||
return;
|
||||
|
||||
keys[name] = value;
|
||||
|
||||
@@ -32,11 +32,9 @@ namespace OpenRA
|
||||
|
||||
var parts = s.Split(' ');
|
||||
|
||||
Keycode key;
|
||||
if (!Enum<Keycode>.TryParse(parts[0], true, out key))
|
||||
if (!Enum<Keycode>.TryParse(parts[0], true, out var key))
|
||||
{
|
||||
int c;
|
||||
if (!int.TryParse(parts[0], out c))
|
||||
if (!int.TryParse(parts[0], out var c))
|
||||
return false;
|
||||
key = (Keycode)c;
|
||||
}
|
||||
|
||||
@@ -498,8 +498,7 @@ namespace OpenRA
|
||||
|
||||
public static string DisplayString(Keycode k)
|
||||
{
|
||||
string ret;
|
||||
if (!KeyNames.TryGetValue(k, out ret))
|
||||
if (!KeyNames.TryGetValue(k, out var ret))
|
||||
return k.ToString();
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -14,7 +14,6 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
@@ -35,9 +34,9 @@ namespace OpenRA
|
||||
mods = GetInstalledMods(searchPaths, explicitPaths);
|
||||
}
|
||||
|
||||
static IEnumerable<Pair<string, string>> GetCandidateMods(IEnumerable<string> searchPaths)
|
||||
static IEnumerable<(string Id, string Path)> GetCandidateMods(IEnumerable<string> searchPaths)
|
||||
{
|
||||
var mods = new List<Pair<string, string>>();
|
||||
var mods = new List<(string, string)>();
|
||||
foreach (var path in searchPaths)
|
||||
{
|
||||
try
|
||||
@@ -48,7 +47,7 @@ namespace OpenRA
|
||||
|
||||
var directory = new DirectoryInfo(resolved);
|
||||
foreach (var subdir in directory.EnumerateDirectories())
|
||||
mods.Add(Pair.New(subdir.Name, subdir.FullName));
|
||||
mods.Add((subdir.Name, subdir.FullName));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -79,8 +78,7 @@ namespace OpenRA
|
||||
Log.Write("debug", "Load mod '{0}': {1}".F(path, e));
|
||||
}
|
||||
|
||||
if (package != null)
|
||||
package.Dispose();
|
||||
package?.Dispose();
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -89,13 +87,13 @@ namespace OpenRA
|
||||
{
|
||||
var ret = new Dictionary<string, Manifest>();
|
||||
var candidates = GetCandidateMods(searchPaths)
|
||||
.Concat(explicitPaths.Select(p => Pair.New(Path.GetFileNameWithoutExtension(p), p)));
|
||||
.Concat(explicitPaths.Select(p => (Id: Path.GetFileNameWithoutExtension(p), Path: p)));
|
||||
|
||||
foreach (var pair in candidates)
|
||||
{
|
||||
var mod = LoadMod(pair.First, pair.Second);
|
||||
var mod = LoadMod(pair.Id, pair.Path);
|
||||
if (mod != null)
|
||||
ret[pair.First] = mod;
|
||||
ret[pair.Id] = mod;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -80,8 +80,6 @@ namespace OpenRA
|
||||
{
|
||||
try
|
||||
{
|
||||
innerState = LinkState.Unlinked;
|
||||
|
||||
if (i.Error != null)
|
||||
{
|
||||
innerState = LinkState.ConnectionFailed;
|
||||
@@ -100,6 +98,8 @@ namespace OpenRA
|
||||
else
|
||||
innerState = LinkState.Linked;
|
||||
}
|
||||
else
|
||||
innerState = LinkState.Unlinked;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -108,8 +108,7 @@ namespace OpenRA
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (onComplete != null)
|
||||
onComplete();
|
||||
onComplete?.Invoke();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
/// <summary> Describes what is to be loaded in order to run a mod. </summary>
|
||||
public class Manifest
|
||||
public class Manifest : IDisposable
|
||||
{
|
||||
public readonly string Id;
|
||||
public readonly IReadOnlyPackage Package;
|
||||
@@ -73,7 +73,7 @@ namespace OpenRA
|
||||
|
||||
readonly string[] reservedModuleNames =
|
||||
{
|
||||
"Metadata", "Folders", "MapFolders", "Packages", "Rules",
|
||||
"Include", "Metadata", "Folders", "MapFolders", "Packages", "Rules",
|
||||
"Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
|
||||
"Voices", "Notifications", "Music", "Translations", "TileSets", "ChromeMetrics", "Missions", "Hotkeys",
|
||||
"ServerTraits", "LoadScreen", "SupportsMapsFrom", "SoundFormats", "SpriteFormats",
|
||||
@@ -89,15 +89,32 @@ namespace OpenRA
|
||||
{
|
||||
Id = modId;
|
||||
Package = package;
|
||||
yaml = new MiniYaml(null, MiniYaml.FromStream(package.GetStream("mod.yaml"), "mod.yaml")).ToDictionary();
|
||||
|
||||
var nodes = MiniYaml.FromStream(package.GetStream("mod.yaml"), "mod.yaml");
|
||||
for (var i = nodes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (nodes[i].Key != "Include")
|
||||
continue;
|
||||
|
||||
// Replace `Includes: filename.yaml` with the contents of filename.yaml
|
||||
var filename = nodes[i].Value.Value;
|
||||
var contents = package.GetStream(filename);
|
||||
if (contents == null)
|
||||
throw new YamlException("{0}: File `{1}` not found.".F(nodes[i].Location, filename));
|
||||
|
||||
nodes.RemoveAt(i);
|
||||
nodes.InsertRange(i, MiniYaml.FromStream(contents, filename));
|
||||
}
|
||||
|
||||
// Merge inherited overrides
|
||||
yaml = new MiniYaml(null, MiniYaml.Merge(new[] { nodes })).ToDictionary();
|
||||
|
||||
Metadata = FieldLoader.Load<ModMetadata>(yaml["Metadata"]);
|
||||
|
||||
// TODO: Use fieldloader
|
||||
MapFolders = YamlDictionary(yaml, "MapFolders");
|
||||
|
||||
MiniYaml packages;
|
||||
if (yaml.TryGetValue("Packages", out packages))
|
||||
if (yaml.TryGetValue("Packages", out var packages))
|
||||
Packages = packages.ToDictionary(x => x.Value).AsReadOnly();
|
||||
|
||||
Rules = YamlList(yaml, "Rules");
|
||||
@@ -217,9 +234,8 @@ namespace OpenRA
|
||||
/// </summary>
|
||||
public T Get<T>(ObjectCreator oc) where T : IGlobalModData
|
||||
{
|
||||
MiniYaml data;
|
||||
var t = typeof(T);
|
||||
if (!yaml.TryGetValue(t.Name, out data))
|
||||
if (!yaml.TryGetValue(t.Name, out var data))
|
||||
{
|
||||
// Lazily create the default values if not explicitly defined.
|
||||
return (T)oc.CreateBasic(t);
|
||||
@@ -241,5 +257,14 @@ namespace OpenRA
|
||||
|
||||
return (T)module;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var module in modules)
|
||||
{
|
||||
var disposableModule = module as IDisposable;
|
||||
disposableModule?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,17 +9,29 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public interface IActorInitializer
|
||||
{
|
||||
World World { get; }
|
||||
T Get<T>() where T : IActorInit;
|
||||
U Get<T, U>() where T : IActorInit<U>;
|
||||
bool Contains<T>() where T : IActorInit;
|
||||
T GetOrDefault<T>(TraitInfo info) where T : ActorInit;
|
||||
T Get<T>(TraitInfo info) where T : ActorInit;
|
||||
U GetValue<T, U>(TraitInfo info) where T : ValueActorInit<U>;
|
||||
U GetValue<T, U>(TraitInfo info, U fallback) where T : ValueActorInit<U>;
|
||||
bool Contains<T>(TraitInfo info) where T : ActorInit;
|
||||
|
||||
T GetOrDefault<T>() where T : ActorInit, ISingleInstanceInit;
|
||||
T Get<T>() where T : ActorInit, ISingleInstanceInit;
|
||||
U GetValue<T, U>() where T : ValueActorInit<U>, ISingleInstanceInit;
|
||||
U GetValue<T, U>(U fallback) where T : ValueActorInit<U>, ISingleInstanceInit;
|
||||
bool Contains<T>() where T : ActorInit, ISingleInstanceInit;
|
||||
}
|
||||
|
||||
public class ActorInitializer : IActorInitializer
|
||||
@@ -35,50 +47,228 @@ namespace OpenRA
|
||||
Dict = dict;
|
||||
}
|
||||
|
||||
public T Get<T>() where T : IActorInit { return Dict.Get<T>(); }
|
||||
public U Get<T, U>() where T : IActorInit<U> { return Dict.Get<T>().Value(World); }
|
||||
public bool Contains<T>() where T : IActorInit { return Dict.Contains<T>(); }
|
||||
}
|
||||
|
||||
public interface IActorInit { }
|
||||
|
||||
public interface IActorInit<T> : IActorInit
|
||||
{
|
||||
T Value(World world);
|
||||
}
|
||||
|
||||
public class LocationInit : IActorInit<CPos>
|
||||
{
|
||||
[FieldFromYamlKey]
|
||||
readonly CPos value = CPos.Zero;
|
||||
|
||||
public LocationInit() { }
|
||||
public LocationInit(CPos init) { value = init; }
|
||||
public CPos Value(World world) { return value; }
|
||||
}
|
||||
|
||||
public class OwnerInit : IActorInit<Player>
|
||||
{
|
||||
[FieldFromYamlKey]
|
||||
public readonly string PlayerName = "Neutral";
|
||||
|
||||
Player player;
|
||||
|
||||
public OwnerInit() { }
|
||||
public OwnerInit(string playerName) { PlayerName = playerName; }
|
||||
|
||||
public OwnerInit(Player player)
|
||||
public T GetOrDefault<T>(TraitInfo info) where T : ActorInit
|
||||
{
|
||||
this.player = player;
|
||||
PlayerName = player.InternalName;
|
||||
var inits = Dict.WithInterface<T>();
|
||||
|
||||
// Traits tagged with an instance name prefer inits with the same name.
|
||||
// If a more specific init is not available, fall back to an unnamed init.
|
||||
// If duplicate inits are defined, take the last to match standard yaml override expectations
|
||||
if (info != null && !string.IsNullOrEmpty(info.InstanceName))
|
||||
return inits.LastOrDefault(i => i.InstanceName == info.InstanceName) ??
|
||||
inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
|
||||
|
||||
// Untagged traits will only use untagged inits
|
||||
return inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
|
||||
}
|
||||
|
||||
public T Get<T>(TraitInfo info) where T : ActorInit
|
||||
{
|
||||
var init = GetOrDefault<T>(info);
|
||||
if (init == null)
|
||||
throw new InvalidOperationException("TypeDictionary does not contain instance of type `{0}`".F(typeof(T)));
|
||||
|
||||
return init;
|
||||
}
|
||||
|
||||
public U GetValue<T, U>(TraitInfo info) where T : ValueActorInit<U>
|
||||
{
|
||||
return Get<T>(info).Value;
|
||||
}
|
||||
|
||||
public U GetValue<T, U>(TraitInfo info, U fallback) where T : ValueActorInit<U>
|
||||
{
|
||||
var init = GetOrDefault<T>(info);
|
||||
return init != null ? init.Value : fallback;
|
||||
}
|
||||
|
||||
public bool Contains<T>(TraitInfo info) where T : ActorInit { return GetOrDefault<T>(info) != null; }
|
||||
|
||||
public T GetOrDefault<T>() where T : ActorInit, ISingleInstanceInit
|
||||
{
|
||||
return Dict.GetOrDefault<T>();
|
||||
}
|
||||
|
||||
public T Get<T>() where T : ActorInit, ISingleInstanceInit
|
||||
{
|
||||
return Dict.Get<T>();
|
||||
}
|
||||
|
||||
public U GetValue<T, U>() where T : ValueActorInit<U>, ISingleInstanceInit
|
||||
{
|
||||
return Get<T>().Value;
|
||||
}
|
||||
|
||||
public U GetValue<T, U>(U fallback) where T : ValueActorInit<U>, ISingleInstanceInit
|
||||
{
|
||||
var init = GetOrDefault<T>();
|
||||
return init != null ? init.Value : fallback;
|
||||
}
|
||||
|
||||
public bool Contains<T>() where T : ActorInit, ISingleInstanceInit { return GetOrDefault<T>() != null; }
|
||||
}
|
||||
|
||||
/*
|
||||
* Things to be aware of when writing ActorInits:
|
||||
*
|
||||
* - ActorReference and ActorGlobal can dynamically create objects without calling a constructor.
|
||||
* The object will be allocated directly then the best matching Initialize() method will be called to set valid state.
|
||||
* - ActorReference will always attempt to call Initialize(MiniYaml). ActorGlobal will use whichever one it first
|
||||
* finds with an argument type that matches the given LuaValue.
|
||||
* - Most ActorInits will want to inherit either ValueActorInit<T> or CompositeActorInit which hide the low-level plumbing.
|
||||
* - Inits that reference actors should use ActorInitActorReference which allows actors to be referenced by name in map.yaml
|
||||
* - Inits that should only have a single instance defined on an actor should implement ISingleInstanceInit to allow
|
||||
* direct queries and runtime enforcement.
|
||||
* - Inits that aren't ISingleInstanceInit should expose a ctor that accepts a TraitInfo to allow per-trait targeting.
|
||||
*/
|
||||
public abstract class ActorInit
|
||||
{
|
||||
[FieldLoader.Ignore]
|
||||
public readonly string InstanceName;
|
||||
|
||||
protected ActorInit(string instanceName)
|
||||
{
|
||||
InstanceName = instanceName;
|
||||
}
|
||||
|
||||
protected ActorInit() { }
|
||||
|
||||
public abstract MiniYaml Save();
|
||||
}
|
||||
|
||||
public interface ISingleInstanceInit { }
|
||||
|
||||
public abstract class ValueActorInit<T> : ActorInit
|
||||
{
|
||||
protected readonly T value;
|
||||
|
||||
protected ValueActorInit(TraitInfo info, T value)
|
||||
: base(info.InstanceName) { this.value = value; }
|
||||
|
||||
protected ValueActorInit(string instanceName, T value)
|
||||
: base(instanceName) { this.value = value; }
|
||||
|
||||
protected ValueActorInit(T value) { this.value = value; }
|
||||
|
||||
public virtual T Value { get { return value; } }
|
||||
|
||||
public virtual void Initialize(MiniYaml yaml)
|
||||
{
|
||||
Initialize((T)FieldLoader.GetValue("value", typeof(T), yaml.Value));
|
||||
}
|
||||
|
||||
public virtual void Initialize(T value)
|
||||
{
|
||||
var field = GetType().GetField("value", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (field != null)
|
||||
field.SetValue(this, value);
|
||||
}
|
||||
|
||||
public override MiniYaml Save()
|
||||
{
|
||||
return new MiniYaml(FieldSaver.FormatValue(value));
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class CompositeActorInit : ActorInit
|
||||
{
|
||||
protected CompositeActorInit(TraitInfo info)
|
||||
: base(info.InstanceName) { }
|
||||
|
||||
protected CompositeActorInit()
|
||||
: base() { }
|
||||
|
||||
public virtual void Initialize(MiniYaml yaml)
|
||||
{
|
||||
FieldLoader.Load(this, yaml);
|
||||
}
|
||||
|
||||
public virtual void Initialize(Dictionary<string, object> values)
|
||||
{
|
||||
foreach (var field in GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
|
||||
{
|
||||
var sa = field.GetCustomAttributes<FieldLoader.SerializeAttribute>(false).DefaultIfEmpty(FieldLoader.SerializeAttribute.Default).First();
|
||||
if (!sa.Serialize)
|
||||
continue;
|
||||
|
||||
if (values.TryGetValue(field.Name, out var value))
|
||||
field.SetValue(this, value);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Dictionary<string, Type> InitializeArgs()
|
||||
{
|
||||
var dict = new Dictionary<string, Type>();
|
||||
foreach (var field in GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
|
||||
{
|
||||
var sa = field.GetCustomAttributes<FieldLoader.SerializeAttribute>(false).DefaultIfEmpty(FieldLoader.SerializeAttribute.Default).First();
|
||||
if (!sa.Serialize)
|
||||
continue;
|
||||
|
||||
dict[field.Name] = field.FieldType;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
public override MiniYaml Save()
|
||||
{
|
||||
return FieldSaver.Save(this);
|
||||
}
|
||||
}
|
||||
|
||||
public class LocationInit : ValueActorInit<CPos>, ISingleInstanceInit
|
||||
{
|
||||
public LocationInit(CPos value)
|
||||
: base(value) { }
|
||||
}
|
||||
|
||||
public class OwnerInit : ActorInit, ISingleInstanceInit
|
||||
{
|
||||
public readonly string InternalName;
|
||||
protected readonly Player value;
|
||||
|
||||
public OwnerInit(Player value)
|
||||
{
|
||||
this.value = value;
|
||||
InternalName = value.InternalName;
|
||||
}
|
||||
|
||||
public OwnerInit(string value)
|
||||
{
|
||||
InternalName = value;
|
||||
}
|
||||
|
||||
public Player Value(World world)
|
||||
{
|
||||
if (player != null)
|
||||
return player;
|
||||
return value ?? world.Players.First(x => x.InternalName == InternalName);
|
||||
}
|
||||
|
||||
return world.Players.First(x => x.InternalName == PlayerName);
|
||||
public void Initialize(MiniYaml yaml)
|
||||
{
|
||||
var field = GetType().GetField("InternalName", BindingFlags.Public | BindingFlags.Instance);
|
||||
if (field != null)
|
||||
field.SetValue(this, yaml.Value);
|
||||
}
|
||||
|
||||
public void Initialize(Player player)
|
||||
{
|
||||
var field = GetType().GetField("value", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (field != null)
|
||||
field.SetValue(this, player);
|
||||
}
|
||||
|
||||
public override MiniYaml Save()
|
||||
{
|
||||
return new MiniYaml(InternalName);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class RuntimeFlagInit : ActorInit, ISuppressInitExport
|
||||
{
|
||||
public override MiniYaml Save()
|
||||
{
|
||||
throw new NotImplementedException("RuntimeFlagInit cannot be saved");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -21,13 +25,10 @@ namespace OpenRA
|
||||
public class ActorReference : IEnumerable
|
||||
{
|
||||
public string Type;
|
||||
public TypeDictionary InitDict
|
||||
{
|
||||
get { return initDict.Value; }
|
||||
}
|
||||
|
||||
Lazy<TypeDictionary> initDict;
|
||||
|
||||
internal TypeDictionary InitDict { get { return initDict.Value; } }
|
||||
|
||||
public ActorReference(string type)
|
||||
: this(type, new Dictionary<string, MiniYaml>()) { }
|
||||
|
||||
@@ -38,47 +39,168 @@ namespace OpenRA
|
||||
{
|
||||
var dict = new TypeDictionary();
|
||||
foreach (var i in inits)
|
||||
dict.Add(LoadInit(i.Key, i.Value));
|
||||
{
|
||||
var init = LoadInit(i.Key, i.Value);
|
||||
if (init is ISingleInstanceInit && dict.Contains(init.GetType()))
|
||||
throw new InvalidDataException("Duplicate initializer '{0}'".F(init.GetType().Name));
|
||||
|
||||
dict.Add(init);
|
||||
}
|
||||
|
||||
return dict;
|
||||
});
|
||||
}
|
||||
|
||||
static IActorInit LoadInit(string traitName, MiniYaml my)
|
||||
public ActorReference(string type, TypeDictionary inits)
|
||||
{
|
||||
var info = Game.CreateObject<IActorInit>(traitName + "Init");
|
||||
FieldLoader.Load(info, my);
|
||||
return info;
|
||||
Type = type;
|
||||
initDict = new Lazy<TypeDictionary>(() =>
|
||||
{
|
||||
var dict = new TypeDictionary();
|
||||
foreach (var i in inits)
|
||||
dict.Add(i);
|
||||
return dict;
|
||||
});
|
||||
}
|
||||
|
||||
public MiniYaml Save(Func<object, bool> initFilter = null)
|
||||
static ActorInit LoadInit(string initName, MiniYaml initYaml)
|
||||
{
|
||||
var initInstance = initName.Split(ActorInfo.TraitInstanceSeparator);
|
||||
var type = Game.ModData.ObjectCreator.FindType(initInstance[0] + "Init");
|
||||
if (type == null)
|
||||
throw new InvalidDataException("Unknown initializer type '{0}Init'".F(initInstance[0]));
|
||||
|
||||
var init = (ActorInit)FormatterServices.GetUninitializedObject(type);
|
||||
if (initInstance.Length > 1)
|
||||
type.GetField("InstanceName").SetValue(init, initInstance[1]);
|
||||
|
||||
var loader = type.GetMethod("Initialize", new[] { typeof(MiniYaml) });
|
||||
if (loader == null)
|
||||
throw new InvalidDataException("{0}Init does not define a yaml-assignable type.".F(initInstance[0]));
|
||||
|
||||
loader.Invoke(init, new[] { initYaml });
|
||||
return init;
|
||||
}
|
||||
|
||||
public MiniYaml Save(Func<ActorInit, bool> initFilter = null)
|
||||
{
|
||||
var ret = new MiniYaml(Type);
|
||||
foreach (var init in InitDict)
|
||||
foreach (var o in initDict.Value)
|
||||
{
|
||||
if (init is ISuppressInitExport)
|
||||
var init = o as ActorInit;
|
||||
if (init == null || o is ISuppressInitExport)
|
||||
continue;
|
||||
|
||||
if (initFilter != null && !initFilter(init))
|
||||
continue;
|
||||
|
||||
var initName = init.GetType().Name;
|
||||
ret.Nodes.Add(new MiniYamlNode(initName.Substring(0, initName.Length - 4), FieldSaver.Save(init)));
|
||||
var initTypeName = init.GetType().Name;
|
||||
var initName = initTypeName.Substring(0, initTypeName.Length - 4);
|
||||
if (!string.IsNullOrEmpty(init.InstanceName))
|
||||
initName += ActorInfo.TraitInstanceSeparator + init.InstanceName;
|
||||
|
||||
ret.Nodes.Add(new MiniYamlNode(initName, init.Save()));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// for initialization syntax
|
||||
public void Add(object o) { InitDict.Add(o); }
|
||||
public IEnumerator GetEnumerator() { return InitDict.GetEnumerator(); }
|
||||
public IEnumerator GetEnumerator() { return initDict.Value.GetEnumerator(); }
|
||||
|
||||
public ActorReference Clone()
|
||||
{
|
||||
var clone = new ActorReference(Type);
|
||||
foreach (var init in InitDict)
|
||||
clone.InitDict.Add(init);
|
||||
foreach (var init in initDict.Value)
|
||||
clone.initDict.Value.Add(init);
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
public void Add(ActorInit init)
|
||||
{
|
||||
if (init is ISingleInstanceInit && InitDict.Contains(init.GetType()))
|
||||
throw new InvalidDataException("Duplicate initializer '{0}'".F(init.GetType().Name));
|
||||
|
||||
InitDict.Add(init);
|
||||
}
|
||||
|
||||
public void Remove(ActorInit o) { initDict.Value.Remove(o); }
|
||||
|
||||
public int RemoveAll<T>() where T : ActorInit
|
||||
{
|
||||
var removed = 0;
|
||||
foreach (var o in initDict.Value.WithInterface<T>().ToList())
|
||||
{
|
||||
removed++;
|
||||
initDict.Value.Remove(o);
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetAll<T>() where T : ActorInit
|
||||
{
|
||||
return initDict.Value.WithInterface<T>();
|
||||
}
|
||||
|
||||
public T GetOrDefault<T>(TraitInfo info) where T : ActorInit
|
||||
{
|
||||
var inits = initDict.Value.WithInterface<T>();
|
||||
|
||||
// Traits tagged with an instance name prefer inits with the same name.
|
||||
// If a more specific init is not available, fall back to an unnamed init.
|
||||
// If duplicate inits are defined, take the last to match standard yaml override expectations
|
||||
if (info != null && !string.IsNullOrEmpty(info.InstanceName))
|
||||
return inits.LastOrDefault(i => i.InstanceName == info.InstanceName) ??
|
||||
inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
|
||||
|
||||
// Untagged traits will only use untagged inits
|
||||
return inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
|
||||
}
|
||||
|
||||
public T Get<T>(TraitInfo info) where T : ActorInit
|
||||
{
|
||||
var init = GetOrDefault<T>(info);
|
||||
if (init == null)
|
||||
throw new InvalidOperationException("TypeDictionary does not contain instance of type `{0}`".F(typeof(T)));
|
||||
|
||||
return init;
|
||||
}
|
||||
|
||||
public U GetValue<T, U>(TraitInfo info) where T : ValueActorInit<U>
|
||||
{
|
||||
return Get<T>(info).Value;
|
||||
}
|
||||
|
||||
public U GetValue<T, U>(TraitInfo info, U fallback) where T : ValueActorInit<U>
|
||||
{
|
||||
var init = GetOrDefault<T>(info);
|
||||
return init != null ? init.Value : fallback;
|
||||
}
|
||||
|
||||
public bool Contains<T>(TraitInfo info) where T : ActorInit { return GetOrDefault<T>(info) != null; }
|
||||
|
||||
public T GetOrDefault<T>() where T : ActorInit, ISingleInstanceInit
|
||||
{
|
||||
return initDict.Value.GetOrDefault<T>();
|
||||
}
|
||||
|
||||
public T Get<T>() where T : ActorInit, ISingleInstanceInit
|
||||
{
|
||||
return initDict.Value.Get<T>();
|
||||
}
|
||||
|
||||
public U GetValue<T, U>() where T : ValueActorInit<U>, ISingleInstanceInit
|
||||
{
|
||||
return Get<T>().Value;
|
||||
}
|
||||
|
||||
public U GetValue<T, U>(U fallback) where T : ValueActorInit<U>, ISingleInstanceInit
|
||||
{
|
||||
var init = GetOrDefault<T>();
|
||||
return init != null ? init.Value : fallback;
|
||||
}
|
||||
|
||||
public bool Contains<T>() where T : ActorInit, ISingleInstanceInit { return GetOrDefault<T>() != null; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,42 +10,28 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
// Represents a layer of "something" that covers the map
|
||||
public class CellLayer<T> : IEnumerable<T>
|
||||
public sealed class CellLayer<T> : CellLayerBase<T>
|
||||
{
|
||||
public readonly Size Size;
|
||||
readonly Rectangle bounds;
|
||||
public readonly MapGridType GridType;
|
||||
public event Action<CPos> CellEntryChanged = null;
|
||||
|
||||
readonly T[] entries;
|
||||
|
||||
public CellLayer(Map map)
|
||||
: this(map.Grid.Type, new Size(map.MapSize.X, map.MapSize.Y)) { }
|
||||
: base(map) { }
|
||||
|
||||
public CellLayer(MapGridType gridType, Size size)
|
||||
{
|
||||
Size = size;
|
||||
bounds = new Rectangle(0, 0, Size.Width, Size.Height);
|
||||
GridType = gridType;
|
||||
entries = new T[size.Width * size.Height];
|
||||
}
|
||||
: base(gridType, size) { }
|
||||
|
||||
public void CopyValuesFrom(CellLayer<T> anotherLayer)
|
||||
public override void CopyValuesFrom(CellLayerBase<T> anotherLayer)
|
||||
{
|
||||
if (Size != anotherLayer.Size || GridType != anotherLayer.GridType)
|
||||
throw new ArgumentException(
|
||||
"layers must have a matching size and shape (grid type).", "anotherLayer");
|
||||
if (CellEntryChanged != null)
|
||||
throw new InvalidOperationException(
|
||||
"Cannot copy values when there are listeners attached to the CellEntryChanged event.");
|
||||
Array.Copy(anotherLayer.entries, entries, entries.Length);
|
||||
|
||||
base.CopyValuesFrom(anotherLayer);
|
||||
}
|
||||
|
||||
public static CellLayer<T> CreateInstance(Func<MPos, T> initialCellValueFactory, Size size, MapGridType mapGridType)
|
||||
@@ -87,8 +73,7 @@ namespace OpenRA
|
||||
{
|
||||
entries[Index(cell)] = value;
|
||||
|
||||
if (CellEntryChanged != null)
|
||||
CellEntryChanged(cell);
|
||||
CellEntryChanged?.Invoke(cell);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,28 +89,10 @@ namespace OpenRA
|
||||
{
|
||||
entries[Index(uv)] = value;
|
||||
|
||||
if (CellEntryChanged != null)
|
||||
CellEntryChanged(uv.ToCPos(GridType));
|
||||
CellEntryChanged?.Invoke(uv.ToCPos(GridType));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Clears the layer contents with a known value</summary>
|
||||
public void Clear(T clearValue)
|
||||
{
|
||||
for (var i = 0; i < entries.Length; i++)
|
||||
entries[i] = clearValue;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<T>)entries).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public bool Contains(CPos cell)
|
||||
{
|
||||
// .ToMPos() returns the same result if the X and Y coordinates
|
||||
|
||||
63
OpenRA.Game/Map/CellLayerBase.cs
Normal file
63
OpenRA.Game/Map/CellLayerBase.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public abstract class CellLayerBase<T> : IEnumerable<T>
|
||||
{
|
||||
public readonly Size Size;
|
||||
public readonly MapGridType GridType;
|
||||
|
||||
protected readonly T[] entries;
|
||||
protected readonly Rectangle bounds;
|
||||
|
||||
public CellLayerBase(Map map)
|
||||
: this(map.Grid.Type, new Size(map.MapSize.X, map.MapSize.Y)) { }
|
||||
|
||||
public CellLayerBase(MapGridType gridType, Size size)
|
||||
{
|
||||
Size = size;
|
||||
bounds = new Rectangle(0, 0, Size.Width, Size.Height);
|
||||
GridType = gridType;
|
||||
entries = new T[size.Width * size.Height];
|
||||
}
|
||||
|
||||
public virtual void CopyValuesFrom(CellLayerBase<T> anotherLayer)
|
||||
{
|
||||
if (Size != anotherLayer.Size || GridType != anotherLayer.GridType)
|
||||
throw new ArgumentException("Layers must have a matching size and shape (grid type).", "anotherLayer");
|
||||
|
||||
Array.Copy(anotherLayer.entries, entries, entries.Length);
|
||||
}
|
||||
|
||||
/// <summary>Clears the layer contents with a known value</summary>
|
||||
public void Clear(T clearValue)
|
||||
{
|
||||
for (var i = 0; i < entries.Length; i++)
|
||||
entries[i] = clearValue;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<T>)entries).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,7 @@ namespace OpenRA
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public sealed class CellRegionEnumerator : IEnumerator<CPos>
|
||||
public struct CellRegionEnumerator : IEnumerator<CPos>
|
||||
{
|
||||
readonly CellRegion r;
|
||||
|
||||
@@ -128,9 +128,11 @@ namespace OpenRA
|
||||
CPos current;
|
||||
|
||||
public CellRegionEnumerator(CellRegion region)
|
||||
: this()
|
||||
{
|
||||
r = region;
|
||||
Reset();
|
||||
current = new MPos(u, v).ToCPos(r.gridType);
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
|
||||
@@ -230,9 +230,10 @@ namespace OpenRA
|
||||
public CellLayer<TerrainTile> Tiles { get; private set; }
|
||||
public CellLayer<ResourceTile> Resources { get; private set; }
|
||||
public CellLayer<byte> Height { get; private set; }
|
||||
public CellLayer<byte> Ramp { get; private set; }
|
||||
public CellLayer<byte> CustomTerrain { get; private set; }
|
||||
|
||||
public ProjectedCellRegion ProjectedCellBounds { get; private set; }
|
||||
public PPos[] ProjectedCells { get; private set; }
|
||||
public CellRegion AllCells { get; private set; }
|
||||
public List<CPos> AllEdgeCells { get; private set; }
|
||||
|
||||
@@ -301,10 +302,12 @@ namespace OpenRA
|
||||
Tiles = new CellLayer<TerrainTile>(Grid.Type, size);
|
||||
Resources = new CellLayer<ResourceTile>(Grid.Type, size);
|
||||
Height = new CellLayer<byte>(Grid.Type, size);
|
||||
Ramp = new CellLayer<byte>(Grid.Type, size);
|
||||
if (Grid.MaximumTerrainHeight > 0)
|
||||
{
|
||||
Height.CellEntryChanged += UpdateProjection;
|
||||
Tiles.CellEntryChanged += UpdateProjection;
|
||||
Tiles.CellEntryChanged += UpdateRamp;
|
||||
}
|
||||
|
||||
Tiles.Clear(tileRef);
|
||||
@@ -328,6 +331,9 @@ namespace OpenRA
|
||||
throw new InvalidDataException("Map format {0} is not supported.\n File: {1}".F(MapFormat, package.Name));
|
||||
|
||||
PlayerDefinitions = MiniYaml.NodesOrEmpty(yaml, "Players");
|
||||
if (PlayerDefinitions.Count > 64)
|
||||
throw new InvalidDataException("Maps must not define more than 64 players.\n File: {0}".F(package.Name));
|
||||
|
||||
ActorDefinitions = MiniYaml.NodesOrEmpty(yaml, "Actors");
|
||||
|
||||
Grid = modData.Manifest.Get<MapGrid>();
|
||||
@@ -336,6 +342,7 @@ namespace OpenRA
|
||||
Tiles = new CellLayer<TerrainTile>(Grid.Type, size);
|
||||
Resources = new CellLayer<ResourceTile>(Grid.Type, size);
|
||||
Height = new CellLayer<byte>(Grid.Type, size);
|
||||
Ramp = new CellLayer<byte>(Grid.Type, size);
|
||||
|
||||
using (var s = Package.GetStream("map.bin"))
|
||||
{
|
||||
@@ -384,6 +391,7 @@ namespace OpenRA
|
||||
|
||||
if (Grid.MaximumTerrainHeight > 0)
|
||||
{
|
||||
Tiles.CellEntryChanged += UpdateRamp;
|
||||
Tiles.CellEntryChanged += UpdateProjection;
|
||||
Height.CellEntryChanged += UpdateProjection;
|
||||
}
|
||||
@@ -422,9 +430,23 @@ namespace OpenRA
|
||||
foreach (var uv in AllCells.MapCoords)
|
||||
CustomTerrain[uv] = byte.MaxValue;
|
||||
|
||||
// Cache initial ramp state
|
||||
var tileset = Rules.TileSet;
|
||||
foreach (var uv in AllCells)
|
||||
{
|
||||
var tile = tileset.GetTileInfo(Tiles[uv]);
|
||||
Ramp[uv] = tile != null ? tile.RampType : (byte)0;
|
||||
}
|
||||
|
||||
AllEdgeCells = UpdateEdgeCells();
|
||||
}
|
||||
|
||||
void UpdateRamp(CPos cell)
|
||||
{
|
||||
var tile = Rules.TileSet.GetTileInfo(Tiles[cell]);
|
||||
Ramp[cell] = tile != null ? tile.RampType : (byte)0;
|
||||
}
|
||||
|
||||
void InitializeCellProjection()
|
||||
{
|
||||
if (initializedCellProjection)
|
||||
@@ -531,12 +553,8 @@ namespace OpenRA
|
||||
return new[] { (PPos)uv };
|
||||
|
||||
// Odd-height ramps get bumped up a level to the next even height layer
|
||||
if ((height & 1) == 1)
|
||||
{
|
||||
var ti = Rules.TileSet.GetTileInfo(Tiles[uv]);
|
||||
if (ti != null && ti.RampType != 0)
|
||||
height += 1;
|
||||
}
|
||||
if ((height & 1) == 1 && Ramp[uv] != 0)
|
||||
height += 1;
|
||||
|
||||
var candidates = new List<PPos>();
|
||||
|
||||
@@ -647,12 +665,40 @@ namespace OpenRA
|
||||
return dataStream.ToArray();
|
||||
}
|
||||
|
||||
public (Color Left, Color Right) GetTerrainColorPair(MPos uv)
|
||||
{
|
||||
Color left, right;
|
||||
var tileset = Rules.TileSet;
|
||||
var type = tileset.GetTileInfo(Tiles[uv]);
|
||||
if (type != null)
|
||||
{
|
||||
if (type.MinColor != type.MaxColor)
|
||||
{
|
||||
left = Exts.ColorLerp(Game.CosmeticRandom.NextFloat(), type.MinColor, type.MaxColor);
|
||||
right = Exts.ColorLerp(Game.CosmeticRandom.NextFloat(), type.MinColor, type.MaxColor);
|
||||
}
|
||||
else
|
||||
left = right = type.MinColor;
|
||||
|
||||
if (tileset.MinHeightColorBrightness != 1.0f || tileset.MaxHeightColorBrightness != 1.0f)
|
||||
{
|
||||
var scale = float2.Lerp(tileset.MinHeightColorBrightness, tileset.MaxHeightColorBrightness, Height[uv] * 1f / Grid.MaximumTerrainHeight);
|
||||
left = Color.FromArgb((int)(scale * left.R).Clamp(0, 255), (int)(scale * left.G).Clamp(0, 255), (int)(scale * left.B).Clamp(0, 255));
|
||||
right = Color.FromArgb((int)(scale * right.R).Clamp(0, 255), (int)(scale * right.G).Clamp(0, 255), (int)(scale * right.B).Clamp(0, 255));
|
||||
}
|
||||
}
|
||||
else
|
||||
left = right = Color.Black;
|
||||
|
||||
return (left, right);
|
||||
}
|
||||
|
||||
public byte[] SavePreview()
|
||||
{
|
||||
var tileset = Rules.TileSet;
|
||||
var actorTypes = Rules.Actors.Values.Where(a => a.HasTraitInfo<IMapPreviewSignatureInfo>());
|
||||
var actors = ActorDefinitions.Where(a => actorTypes.Where(ai => ai.Name == a.Value.Value).Any());
|
||||
var positions = new List<Pair<MPos, Color>>();
|
||||
var positions = new List<(MPos Position, Color Color)>();
|
||||
foreach (var actor in actors)
|
||||
{
|
||||
var s = new ActorReference(actor.Value.Value, actor.Value.ToDictionary());
|
||||
@@ -686,24 +732,18 @@ namespace OpenRA
|
||||
var stride = bitmapWidth * 4;
|
||||
var pxStride = 4;
|
||||
var minimapData = new byte[stride * height];
|
||||
Color leftColor, rightColor;
|
||||
(Color Left, Color Right) terrainColor = default((Color, Color));
|
||||
|
||||
for (var y = 0; y < height; y++)
|
||||
{
|
||||
for (var x = 0; x < width; x++)
|
||||
{
|
||||
var uv = new MPos(x + Bounds.Left, y + Bounds.Top);
|
||||
var actorsThere = positions.Where(ap => ap.First == uv);
|
||||
if (actorsThere.Any())
|
||||
{
|
||||
leftColor = rightColor = actorsThere.First().Second;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cell contains terrain
|
||||
var type = tileset.GetTileInfo(Tiles[uv]);
|
||||
leftColor = type != null ? type.LeftColor : Color.Black;
|
||||
rightColor = type != null ? type.RightColor : Color.Black;
|
||||
}
|
||||
|
||||
// FirstOrDefault will return a (MPos.Zero, Color.Transparent) if positions is empty
|
||||
var actorColor = positions.FirstOrDefault(ap => ap.Position == uv).Color;
|
||||
if (actorColor.A == 0)
|
||||
terrainColor = GetTerrainColorPair(uv);
|
||||
|
||||
if (isRectangularIsometric)
|
||||
{
|
||||
@@ -713,28 +753,31 @@ namespace OpenRA
|
||||
if (x + dx > 0)
|
||||
{
|
||||
var z = y * stride + xOffset - pxStride;
|
||||
minimapData[z++] = leftColor.R;
|
||||
minimapData[z++] = leftColor.G;
|
||||
minimapData[z++] = leftColor.B;
|
||||
minimapData[z++] = leftColor.A;
|
||||
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
|
||||
minimapData[z++] = c.R;
|
||||
minimapData[z++] = c.G;
|
||||
minimapData[z++] = c.B;
|
||||
minimapData[z] = c.A;
|
||||
}
|
||||
|
||||
if (xOffset < stride)
|
||||
{
|
||||
var z = y * stride + xOffset;
|
||||
minimapData[z++] = rightColor.R;
|
||||
minimapData[z++] = rightColor.G;
|
||||
minimapData[z++] = rightColor.B;
|
||||
minimapData[z++] = rightColor.A;
|
||||
var c = actorColor.A == 0 ? terrainColor.Right : actorColor;
|
||||
minimapData[z++] = c.R;
|
||||
minimapData[z++] = c.G;
|
||||
minimapData[z++] = c.B;
|
||||
minimapData[z] = c.A;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var z = y * stride + pxStride * x;
|
||||
minimapData[z++] = leftColor.R;
|
||||
minimapData[z++] = leftColor.G;
|
||||
minimapData[z++] = leftColor.B;
|
||||
minimapData[z++] = leftColor.A;
|
||||
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
|
||||
minimapData[z++] = c.R;
|
||||
minimapData[z++] = c.G;
|
||||
minimapData[z++] = c.B;
|
||||
minimapData[z] = c.A;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -770,8 +813,9 @@ namespace OpenRA
|
||||
|
||||
bool ContainsAllProjectedCellsCovering(MPos uv)
|
||||
{
|
||||
// PERF: Checking the bounds directly here is the same as calling Contains((PPos)uv) but saves an allocation
|
||||
if (Grid.MaximumTerrainHeight == 0)
|
||||
return Contains((PPos)uv);
|
||||
return Bounds.Contains(uv.U, uv.V);
|
||||
|
||||
// If the cell has no valid projection, then we're off the map.
|
||||
var projectedCells = ProjectedCellsCovering(uv);
|
||||
@@ -781,6 +825,7 @@ namespace OpenRA
|
||||
foreach (var puv in projectedCells)
|
||||
if (!Contains(puv))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -805,7 +850,7 @@ namespace OpenRA
|
||||
// (c) u, v coordinates run diagonally to the cell axes, and we define
|
||||
// 1024 as the length projected onto the primary cell axis
|
||||
// - 512 * sqrt(2) = 724
|
||||
var z = Height.Contains(cell) ? 724 * Height[cell] : 0;
|
||||
var z = Height.Contains(cell) ? 724 * Height[cell] + Grid.Ramps[Ramp[cell]].CenterHeightOffset : 0;
|
||||
return new WPos(724 * (cell.X - cell.Y + 1), 724 * (cell.X + cell.Y + 1), z);
|
||||
}
|
||||
|
||||
@@ -813,15 +858,42 @@ namespace OpenRA
|
||||
{
|
||||
var index = (int)subCell;
|
||||
if (index >= 0 && index < Grid.SubCellOffsets.Length)
|
||||
return CenterOfCell(cell) + Grid.SubCellOffsets[index];
|
||||
{
|
||||
var center = CenterOfCell(cell);
|
||||
var offset = Grid.SubCellOffsets[index];
|
||||
var ramp = Ramp.Contains(cell) ? Ramp[cell] : 0;
|
||||
if (ramp != 0)
|
||||
{
|
||||
var r = Grid.Ramps[ramp];
|
||||
offset += new WVec(0, 0, r.HeightOffset(offset.X, offset.Y) - r.CenterHeightOffset);
|
||||
}
|
||||
|
||||
return center + offset;
|
||||
}
|
||||
|
||||
return CenterOfCell(cell);
|
||||
}
|
||||
|
||||
public WDist DistanceAboveTerrain(WPos pos)
|
||||
{
|
||||
if (Grid.Type == MapGridType.Rectangular)
|
||||
return new WDist(pos.Z);
|
||||
|
||||
// Apply ramp offset
|
||||
var cell = CellContaining(pos);
|
||||
var delta = pos - CenterOfCell(cell);
|
||||
return new WDist(delta.Z);
|
||||
var offset = pos - CenterOfCell(cell);
|
||||
|
||||
if (!Ramp.Contains(cell))
|
||||
return new WDist(offset.Z);
|
||||
|
||||
var ramp = Ramp[cell];
|
||||
if (ramp != 0)
|
||||
{
|
||||
var r = Grid.Ramps[ramp];
|
||||
return new WDist(offset.Z + r.CenterHeightOffset - r.HeightOffset(offset.X, offset.Y));
|
||||
}
|
||||
|
||||
return new WDist(offset.Z);
|
||||
}
|
||||
|
||||
public WVec Offset(CVec delta, int dz)
|
||||
@@ -899,13 +971,13 @@ namespace OpenRA
|
||||
return projectedHeight[(MPos)puv];
|
||||
}
|
||||
|
||||
public int FacingBetween(CPos cell, CPos towards, int fallbackfacing)
|
||||
public WAngle FacingBetween(CPos cell, CPos towards, WAngle fallbackfacing)
|
||||
{
|
||||
var delta = CenterOfCell(towards) - CenterOfCell(cell);
|
||||
if (delta.HorizontalLengthSquared == 0)
|
||||
return fallbackfacing;
|
||||
|
||||
return delta.Yaw.Facing;
|
||||
return delta.Yaw;
|
||||
}
|
||||
|
||||
public void Resize(int width, int height)
|
||||
@@ -913,11 +985,13 @@ namespace OpenRA
|
||||
var oldMapTiles = Tiles;
|
||||
var oldMapResources = Resources;
|
||||
var oldMapHeight = Height;
|
||||
var oldMapRamp = Ramp;
|
||||
var newSize = new Size(width, height);
|
||||
|
||||
Tiles = CellLayer.Resize(oldMapTiles, newSize, oldMapTiles[MPos.Zero]);
|
||||
Resources = CellLayer.Resize(oldMapResources, newSize, oldMapResources[MPos.Zero]);
|
||||
Height = CellLayer.Resize(oldMapHeight, newSize, oldMapHeight[MPos.Zero]);
|
||||
Ramp = CellLayer.Resize(oldMapRamp, newSize, oldMapRamp[MPos.Zero]);
|
||||
MapSize = new int2(newSize);
|
||||
|
||||
var tl = new MPos(0, 0);
|
||||
@@ -947,7 +1021,8 @@ namespace OpenRA
|
||||
ProjectedBottomRight = new WPos(br.U * 1024 - 1, (br.V + 1) * 1024 - 1, 0);
|
||||
}
|
||||
|
||||
ProjectedCellBounds = new ProjectedCellRegion(this, tl, br);
|
||||
// PERF: This enumeration isn't going to change during the game
|
||||
ProjectedCells = new ProjectedCellRegion(this, tl, br).ToArray();
|
||||
}
|
||||
|
||||
public void FixOpenAreas()
|
||||
@@ -1210,16 +1285,12 @@ namespace OpenRA
|
||||
throw new ArgumentOutOfRangeException("maxRange",
|
||||
"The requested range ({0}) cannot exceed the value of MaximumTileSearchRange ({1})".F(maxRange, Grid.MaximumTileSearchRange));
|
||||
|
||||
Func<CPos, bool> valid = Contains;
|
||||
if (allowOutsideBounds)
|
||||
valid = Tiles.Contains;
|
||||
|
||||
for (var i = minRange; i <= maxRange; i++)
|
||||
{
|
||||
foreach (var offset in Grid.TilesByDistance[i])
|
||||
{
|
||||
var t = offset + center;
|
||||
if (valid(t))
|
||||
if (allowOutsideBounds ? Tiles.Contains(t) : Contains(t))
|
||||
yield return t;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ namespace OpenRA
|
||||
object syncRoot = new object();
|
||||
Queue<MapPreview> generateMinimap = new Queue<MapPreview>();
|
||||
|
||||
public Dictionary<string, string> StringPool { get; } = new Dictionary<string, string>();
|
||||
|
||||
public MapCache(ModData modData)
|
||||
{
|
||||
this.modData = modData;
|
||||
@@ -70,13 +72,10 @@ namespace OpenRA
|
||||
try
|
||||
{
|
||||
// HACK: If the path is inside the the support directory then we may need to create it
|
||||
if (Platform.IsPathRelativeToSupportDirectory(name))
|
||||
{
|
||||
// Assume that the path is a directory if there is not an existing file with the same name
|
||||
var resolved = Platform.ResolvePath(name);
|
||||
if (!File.Exists(resolved))
|
||||
Directory.CreateDirectory(resolved);
|
||||
}
|
||||
// Assume that the path is a directory if there is not an existing file with the same name
|
||||
var resolved = Platform.ResolvePath(name);
|
||||
if (resolved.StartsWith(Platform.SupportDir) && !File.Exists(resolved))
|
||||
Directory.CreateDirectory(resolved);
|
||||
|
||||
package = modData.ModFiles.OpenPackage(name);
|
||||
}
|
||||
@@ -111,8 +110,7 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (mapPackage != null)
|
||||
mapPackage.Dispose();
|
||||
mapPackage?.Dispose();
|
||||
Console.WriteLine("Failed to load map: {0}", map);
|
||||
Console.WriteLine("Details: {0}", e);
|
||||
Log.Write("debug", "Failed to load map: {0}", map);
|
||||
@@ -131,8 +129,7 @@ namespace OpenRA
|
||||
// Enumerate map directories
|
||||
foreach (var kv in modData.Manifest.MapFolders)
|
||||
{
|
||||
MapClassification packageClassification;
|
||||
if (!Enum.TryParse(kv.Value, out packageClassification))
|
||||
if (!Enum.TryParse(kv.Value, out MapClassification packageClassification))
|
||||
continue;
|
||||
|
||||
if (!classification.HasFlag(packageClassification))
|
||||
@@ -144,12 +141,9 @@ namespace OpenRA
|
||||
name = name.Substring(1);
|
||||
|
||||
// Don't try to open the map directory in the support directory if it doesn't exist
|
||||
if (Platform.IsPathRelativeToSupportDirectory(name))
|
||||
{
|
||||
var resolved = Platform.ResolvePath(name);
|
||||
if (!Directory.Exists(resolved) || !File.Exists(resolved))
|
||||
continue;
|
||||
}
|
||||
var resolved = Platform.ResolvePath(name);
|
||||
if (resolved.StartsWith(Platform.SupportDir) && (!Directory.Exists(resolved) || !File.Exists(resolved)))
|
||||
continue;
|
||||
|
||||
using (var package = (IReadWritePackage)modData.ModFiles.OpenPackage(name))
|
||||
{
|
||||
@@ -172,6 +166,7 @@ namespace OpenRA
|
||||
public void QueryRemoteMapDetails(string repositoryUrl, IEnumerable<string> uids, Action<MapPreview> mapDetailsReceived = null, Action queryFailed = null)
|
||||
{
|
||||
var maps = uids.Distinct()
|
||||
.Where(uid => uid != null)
|
||||
.Select(uid => previews[uid])
|
||||
.Where(p => p.Status == MapStatus.Unavailable)
|
||||
.ToDictionary(p => p.Uid, p => p);
|
||||
@@ -193,8 +188,7 @@ namespace OpenRA
|
||||
foreach (var p in maps.Values)
|
||||
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
|
||||
|
||||
if (queryFailed != null)
|
||||
queryFailed();
|
||||
queryFailed?.Invoke();
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -214,8 +208,7 @@ namespace OpenRA
|
||||
{
|
||||
Log.Write("debug", "Can't parse remote map search data:\n{0}", data);
|
||||
Log.Write("debug", "Exception: {0}", e);
|
||||
if (queryFailed != null)
|
||||
queryFailed();
|
||||
queryFailed?.Invoke();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -299,8 +292,7 @@ namespace OpenRA
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
// Wait for any existing thread to exit before starting a new one.
|
||||
if (previewLoaderThread != null)
|
||||
previewLoaderThread.Join();
|
||||
previewLoaderThread?.Join();
|
||||
|
||||
previewLoaderThread = new Thread(LoadAsyncInternal)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -20,6 +19,85 @@ namespace OpenRA
|
||||
{
|
||||
public enum MapGridType { Rectangular, RectangularIsometric }
|
||||
|
||||
public enum RampSplit { Flat, X, Y }
|
||||
public enum RampCornerHeight { Low = 0, Half = 1, Full = 2 }
|
||||
|
||||
public struct CellRamp
|
||||
{
|
||||
public readonly int CenterHeightOffset;
|
||||
public readonly WVec[] Corners;
|
||||
public readonly WVec[][] Polygons;
|
||||
|
||||
public CellRamp(MapGridType type, RampCornerHeight tl = RampCornerHeight.Low, RampCornerHeight tr = RampCornerHeight.Low, RampCornerHeight br = RampCornerHeight.Low, RampCornerHeight bl = RampCornerHeight.Low, RampSplit split = RampSplit.Flat)
|
||||
{
|
||||
if (type == MapGridType.RectangularIsometric)
|
||||
{
|
||||
Corners = new[]
|
||||
{
|
||||
new WVec(0, -724, 724 * (int)tl),
|
||||
new WVec(724, 0, 724 * (int)tr),
|
||||
new WVec(0, 724, 724 * (int)br),
|
||||
new WVec(-724, 0, 724 * (int)bl),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
Corners = new[]
|
||||
{
|
||||
new WVec(-512, -512, 512 * (int)tl),
|
||||
new WVec(512, -512, 512 * (int)tr),
|
||||
new WVec(512, 512, 512 * (int)br),
|
||||
new WVec(-512, 512, 512 * (int)bl)
|
||||
};
|
||||
}
|
||||
|
||||
if (split == RampSplit.X)
|
||||
{
|
||||
Polygons = new[]
|
||||
{
|
||||
new[] { Corners[0], Corners[1], Corners[3] },
|
||||
new[] { Corners[1], Corners[2], Corners[3] }
|
||||
};
|
||||
}
|
||||
else if (split == RampSplit.Y)
|
||||
{
|
||||
Polygons = new[]
|
||||
{
|
||||
new[] { Corners[0], Corners[1], Corners[2] },
|
||||
new[] { Corners[0], Corners[2], Corners[3] }
|
||||
};
|
||||
}
|
||||
else
|
||||
Polygons = new[] { Corners };
|
||||
|
||||
// Initial value must be asigned before HeightOffset can be called
|
||||
CenterHeightOffset = 0;
|
||||
CenterHeightOffset = HeightOffset(0, 0);
|
||||
}
|
||||
|
||||
public int HeightOffset(int dX, int dY)
|
||||
{
|
||||
// Enumerate over the polygons, assuming that they are triangles
|
||||
// If the ramp is not split we will take the first three vertices of the corners as a valid triangle
|
||||
WVec[] p = null;
|
||||
var u = 0;
|
||||
var v = 0;
|
||||
for (var i = 0; i < Polygons.Length; i++)
|
||||
{
|
||||
p = Polygons[i];
|
||||
u = ((p[1].Y - p[2].Y) * (dX - p[2].X) - (p[1].X - p[2].X) * (dY - p[2].Y)) / 1024;
|
||||
v = ((p[0].X - p[2].X) * (dY - p[2].Y) - (p[0].Y - p[2].Y) * (dX - p[2].X)) / 1024;
|
||||
|
||||
// Point is within the triangle if 0 <= u,v <= 1024
|
||||
if (u >= 0 && u <= 1024 && v >= 0 && v <= 1024)
|
||||
break;
|
||||
}
|
||||
|
||||
// Calculate w from u,v and interpolate height
|
||||
return (u * p[0].Z + v * p[1].Z + (1024 - u - v) * p[2].Z) / 1024;
|
||||
}
|
||||
}
|
||||
|
||||
public class MapGrid : IGlobalModData
|
||||
{
|
||||
public readonly MapGridType Type = MapGridType.Rectangular;
|
||||
@@ -41,43 +119,7 @@ namespace OpenRA
|
||||
new WVec(256, 256, 0), // bottom right - index 5
|
||||
};
|
||||
|
||||
public WVec[][] CellCorners { get; private set; }
|
||||
|
||||
readonly int[][] cellCornerHalfHeights = new int[][]
|
||||
{
|
||||
// Flat
|
||||
new[] { 0, 0, 0, 0 },
|
||||
|
||||
// Slopes (two corners high)
|
||||
new[] { 0, 0, 1, 1 },
|
||||
new[] { 1, 0, 0, 1 },
|
||||
new[] { 1, 1, 0, 0 },
|
||||
new[] { 0, 1, 1, 0 },
|
||||
|
||||
// Slopes (one corner high)
|
||||
new[] { 0, 0, 0, 1 },
|
||||
new[] { 1, 0, 0, 0 },
|
||||
new[] { 0, 1, 0, 0 },
|
||||
new[] { 0, 0, 1, 0 },
|
||||
|
||||
// Slopes (three corners high)
|
||||
new[] { 1, 0, 1, 1 },
|
||||
new[] { 1, 1, 0, 1 },
|
||||
new[] { 1, 1, 1, 0 },
|
||||
new[] { 0, 1, 1, 1 },
|
||||
|
||||
// Slopes (two corners high, one corner double high)
|
||||
new[] { 1, 0, 1, 2 },
|
||||
new[] { 2, 1, 0, 1 },
|
||||
new[] { 1, 2, 1, 0 },
|
||||
new[] { 0, 1, 2, 1 },
|
||||
|
||||
// Slopes (two corners high, alternating)
|
||||
new[] { 1, 0, 1, 0 },
|
||||
new[] { 0, 1, 0, 1 },
|
||||
new[] { 1, 0, 1, 0 },
|
||||
new[] { 0, 1, 0, 1 }
|
||||
};
|
||||
public CellRamp[] Ramps { get; private set; }
|
||||
|
||||
internal readonly CVec[][] TilesByDistance;
|
||||
|
||||
@@ -96,34 +138,46 @@ namespace OpenRA
|
||||
throw new InvalidDataException("Subcell default index must be a valid index into the offset triples and must be greater than 0 for mods with subcells");
|
||||
}
|
||||
|
||||
var makeCorners = Type == MapGridType.RectangularIsometric ?
|
||||
(Func<int[], WVec[]>)IsometricCellCorners : RectangularCellCorners;
|
||||
CellCorners = cellCornerHalfHeights.Select(makeCorners).ToArray();
|
||||
// Slope types are hardcoded following the convention from the TS and RA2 map format
|
||||
Ramps = new[]
|
||||
{
|
||||
// Flat
|
||||
new CellRamp(Type),
|
||||
|
||||
// Two adjacent corners raised by half a cell
|
||||
new CellRamp(Type, tr: RampCornerHeight.Half, br: RampCornerHeight.Half),
|
||||
new CellRamp(Type, br: RampCornerHeight.Half, bl: RampCornerHeight.Half),
|
||||
new CellRamp(Type, tl: RampCornerHeight.Half, bl: RampCornerHeight.Half),
|
||||
new CellRamp(Type, tl: RampCornerHeight.Half, tr: RampCornerHeight.Half),
|
||||
|
||||
// One corner raised by half a cell
|
||||
new CellRamp(Type, br: RampCornerHeight.Half, split: RampSplit.X),
|
||||
new CellRamp(Type, bl: RampCornerHeight.Half, split: RampSplit.Y),
|
||||
new CellRamp(Type, tl: RampCornerHeight.Half, split: RampSplit.X),
|
||||
new CellRamp(Type, tr: RampCornerHeight.Half, split: RampSplit.Y),
|
||||
|
||||
// Three corners raised by half a cell
|
||||
new CellRamp(Type, tr: RampCornerHeight.Half, br: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.X),
|
||||
new CellRamp(Type, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.Y),
|
||||
new CellRamp(Type, tl: RampCornerHeight.Half, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.X),
|
||||
new CellRamp(Type, tl: RampCornerHeight.Half, tr: RampCornerHeight.Half, br: RampCornerHeight.Half, split: RampSplit.Y),
|
||||
|
||||
// Full tile sloped (mid corners raised by half cell, far corner by full cell)
|
||||
new CellRamp(Type, tr: RampCornerHeight.Half, br: RampCornerHeight.Full, bl: RampCornerHeight.Half),
|
||||
new CellRamp(Type, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, bl: RampCornerHeight.Full),
|
||||
new CellRamp(Type, tl: RampCornerHeight.Full, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half),
|
||||
new CellRamp(Type, tl: RampCornerHeight.Half, tr: RampCornerHeight.Full, br: RampCornerHeight.Half),
|
||||
|
||||
// Two opposite corners raised by half a cell
|
||||
new CellRamp(Type, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.Y),
|
||||
new CellRamp(Type, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, split: RampSplit.Y),
|
||||
new CellRamp(Type, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.X),
|
||||
new CellRamp(Type, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, split: RampSplit.X),
|
||||
};
|
||||
|
||||
TilesByDistance = CreateTilesByDistance();
|
||||
}
|
||||
|
||||
static WVec[] IsometricCellCorners(int[] cornerHeight)
|
||||
{
|
||||
return new WVec[]
|
||||
{
|
||||
new WVec(-724, 0, 724 * cornerHeight[0]),
|
||||
new WVec(0, -724, 724 * cornerHeight[1]),
|
||||
new WVec(724, 0, 724 * cornerHeight[2]),
|
||||
new WVec(0, 724, 724 * cornerHeight[3])
|
||||
};
|
||||
}
|
||||
|
||||
static WVec[] RectangularCellCorners(int[] cornerHeight)
|
||||
{
|
||||
return new WVec[]
|
||||
{
|
||||
new WVec(-512, -512, 512 * cornerHeight[0]),
|
||||
new WVec(512, -512, 512 * cornerHeight[1]),
|
||||
new WVec(512, 512, 512 * cornerHeight[2]),
|
||||
new WVec(-512, 512, 512 * cornerHeight[3])
|
||||
};
|
||||
}
|
||||
|
||||
CVec[][] CreateTilesByDistance()
|
||||
{
|
||||
var ts = new List<CVec>[MaximumTileSearchRange + 1];
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace OpenRA
|
||||
public bool DefinesUnsafeCustomRules { get; private set; }
|
||||
public bool RulesLoaded { get; private set; }
|
||||
|
||||
public void SetRulesetGenerator(ModData modData, Func<Pair<Ruleset, bool>> generator)
|
||||
public void SetRulesetGenerator(ModData modData, Func<(Ruleset Ruleset, bool DefinesUnsafeCustomRules)> generator)
|
||||
{
|
||||
InvalidCustomRules = false;
|
||||
RulesLoaded = false;
|
||||
@@ -106,8 +106,8 @@ namespace OpenRA
|
||||
try
|
||||
{
|
||||
var ret = generator();
|
||||
DefinesUnsafeCustomRules = ret.Second;
|
||||
return ret.First;
|
||||
DefinesUnsafeCustomRules = ret.DefinesUnsafeCustomRules;
|
||||
return ret.Ruleset;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -223,7 +223,7 @@ namespace OpenRA
|
||||
if (yamlStream == null)
|
||||
throw new FileNotFoundException("Required file map.yaml not present in this map");
|
||||
|
||||
yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, "map.yaml")).ToDictionary();
|
||||
yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, "map.yaml", stringPool: cache.StringPool)).ToDictionary();
|
||||
}
|
||||
|
||||
Package = p;
|
||||
@@ -233,8 +233,7 @@ namespace OpenRA
|
||||
newData.GridType = gridType;
|
||||
newData.Class = classification;
|
||||
|
||||
MiniYaml temp;
|
||||
if (yaml.TryGetValue("MapFormat", out temp))
|
||||
if (yaml.TryGetValue("MapFormat", out var temp))
|
||||
{
|
||||
var format = FieldLoader.GetValue<int>("MapFormat", temp.Value);
|
||||
if (format != Map.SupportedMapFormat)
|
||||
@@ -269,14 +268,13 @@ namespace OpenRA
|
||||
try
|
||||
{
|
||||
// Actor definitions may change if the map format changes
|
||||
MiniYaml actorDefinitions;
|
||||
if (yaml.TryGetValue("Actors", out actorDefinitions))
|
||||
if (yaml.TryGetValue("Actors", out var actorDefinitions))
|
||||
{
|
||||
var spawns = new List<CPos>();
|
||||
foreach (var kv in actorDefinitions.Nodes.Where(d => d.Value.Value == "mpspawn"))
|
||||
{
|
||||
var s = new ActorReference(kv.Value.Value, kv.Value.ToDictionary());
|
||||
spawns.Add(s.InitDict.Get<LocationInit>().Value(null));
|
||||
spawns.Add(s.Get<LocationInit>().Value);
|
||||
}
|
||||
|
||||
newData.SpawnPoints = spawns.ToArray();
|
||||
@@ -293,8 +291,7 @@ namespace OpenRA
|
||||
try
|
||||
{
|
||||
// Player definitions may change if the map format changes
|
||||
MiniYaml playerDefinitions;
|
||||
if (yaml.TryGetValue("Players", out playerDefinitions))
|
||||
if (yaml.TryGetValue("Players", out var playerDefinitions))
|
||||
{
|
||||
newData.Players = new MapPlayers(playerDefinitions.Nodes);
|
||||
newData.PlayerCount = newData.Players.Players.Count(x => x.Value.Playable);
|
||||
@@ -318,7 +315,7 @@ namespace OpenRA
|
||||
voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions, modelSequenceDefinitions);
|
||||
var flagged = Ruleset.DefinesUnsafeCustomRules(modData, this, ruleDefinitions,
|
||||
weaponDefinitions, voiceDefinitions, notificationDefinitions, sequenceDefinitions);
|
||||
return Pair.New(rules, flagged);
|
||||
return (rules, flagged);
|
||||
});
|
||||
|
||||
if (p.Contains("map.png"))
|
||||
@@ -331,8 +328,7 @@ namespace OpenRA
|
||||
|
||||
MiniYaml LoadRuleSection(Dictionary<string, MiniYaml> yaml, string section)
|
||||
{
|
||||
MiniYaml node;
|
||||
if (!yaml.TryGetValue(section, out node))
|
||||
if (!yaml.TryGetValue(section, out var node))
|
||||
return null;
|
||||
|
||||
return node;
|
||||
@@ -402,7 +398,7 @@ namespace OpenRA
|
||||
voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions, modelSequenceDefinitions);
|
||||
var flagged = Ruleset.DefinesUnsafeCustomRules(modData, this, ruleDefinitions,
|
||||
weaponDefinitions, voiceDefinitions, notificationDefinitions, sequenceDefinitions);
|
||||
return Pair.New(rules, flagged);
|
||||
return (rules, flagged);
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -416,8 +412,7 @@ namespace OpenRA
|
||||
if (innerData.Preview != null)
|
||||
cache.CacheMinimap(this);
|
||||
|
||||
if (parseMetadata != null)
|
||||
parseMetadata(this);
|
||||
parseMetadata?.Invoke(this);
|
||||
}
|
||||
|
||||
// Update the status and class unconditionally
|
||||
@@ -526,9 +521,7 @@ namespace OpenRA
|
||||
public void Delete()
|
||||
{
|
||||
Invalidate();
|
||||
var deleteFromPackage = parentPackage as IReadWritePackage;
|
||||
if (deleteFromPackage != null)
|
||||
deleteFromPackage.Delete(Package.Name);
|
||||
(parentPackage as IReadWritePackage)?.Delete(Package.Name);
|
||||
}
|
||||
|
||||
Stream IReadOnlyFileSystem.Open(string filename)
|
||||
|
||||
@@ -30,14 +30,29 @@ namespace OpenRA
|
||||
public string Faction;
|
||||
|
||||
public bool LockColor = false;
|
||||
public Color Color = Color.FromAhsl(0, 0, 238);
|
||||
public Color Color = Game.ModData.Manifest.Get<DefaultPlayer>().Color;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the "Home" location, which can be used by traits and scripts to e.g. set the initial camera
|
||||
/// location or choose the map edge for reinforcements.
|
||||
/// This will usually be overridden for client (lobby slot) players with a location based on the Spawn index
|
||||
/// </summary>
|
||||
public CPos HomeLocation = CPos.Zero;
|
||||
|
||||
public bool LockSpawn = false;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the initial spawn point index that is used to override the "Home" location for client (lobby slot) players.
|
||||
/// Map players always ignore this and use HomeLocation directly.
|
||||
/// </summary>
|
||||
public int Spawn = 0;
|
||||
|
||||
public bool LockTeam = false;
|
||||
public int Team = 0;
|
||||
|
||||
public bool LockHandicap = false;
|
||||
public int Handicap = 0;
|
||||
|
||||
public string[] Allies = { };
|
||||
public string[] Enemies = { };
|
||||
|
||||
|
||||
62
OpenRA.Game/Map/ProjectedCellLayer.cs
Normal file
62
OpenRA.Game/Map/ProjectedCellLayer.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public sealed class ProjectedCellLayer<T> : CellLayerBase<T>
|
||||
{
|
||||
public ProjectedCellLayer(Map map)
|
||||
: base(map) { }
|
||||
|
||||
public ProjectedCellLayer(MapGridType gridType, Size size)
|
||||
: base(gridType, size) { }
|
||||
|
||||
// Resolve an array index from map coordinates.
|
||||
public int Index(PPos uv)
|
||||
{
|
||||
return uv.V * Size.Width + uv.U;
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return entries[index];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
entries[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the layer contents using projected map coordinates.</summary>
|
||||
public T this[PPos uv]
|
||||
{
|
||||
get
|
||||
{
|
||||
return entries[Index(uv)];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
entries[Index(uv)] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(PPos uv)
|
||||
{
|
||||
return bounds.Contains(uv.U, uv.V);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ namespace OpenRA
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public sealed class ProjectedCellRegionEnumerator : IEnumerator<PPos>
|
||||
public struct ProjectedCellRegionEnumerator : IEnumerator<PPos>
|
||||
{
|
||||
readonly ProjectedCellRegion r;
|
||||
|
||||
@@ -86,9 +86,11 @@ namespace OpenRA
|
||||
PPos current;
|
||||
|
||||
public ProjectedCellRegionEnumerator(ProjectedCellRegion region)
|
||||
: this()
|
||||
{
|
||||
r = region;
|
||||
Reset();
|
||||
current = new PPos(u, v);
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
|
||||
@@ -24,9 +24,8 @@ namespace OpenRA
|
||||
public readonly byte TerrainType = byte.MaxValue;
|
||||
public readonly byte Height;
|
||||
public readonly byte RampType;
|
||||
public readonly Color LeftColor;
|
||||
public readonly Color RightColor;
|
||||
|
||||
public readonly Color MinColor;
|
||||
public readonly Color MaxColor;
|
||||
public readonly float ZOffset = 0.0f;
|
||||
public readonly float ZRamp = 1.0f;
|
||||
}
|
||||
@@ -55,13 +54,6 @@ namespace OpenRA
|
||||
|
||||
readonly TerrainTileInfo[] tileInfo;
|
||||
|
||||
public TerrainTemplateInfo(ushort id, string[] images, int2 size, byte[] tiles)
|
||||
{
|
||||
Id = id;
|
||||
Images = images;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public TerrainTemplateInfo(TileSet tileSet, MiniYaml my)
|
||||
{
|
||||
FieldLoader.Load(this, my);
|
||||
@@ -73,9 +65,11 @@ namespace OpenRA
|
||||
tileInfo = new TerrainTileInfo[Size.X * Size.Y];
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
int key;
|
||||
if (!int.TryParse(node.Key, out key) || key < 0 || key >= tileInfo.Length)
|
||||
throw new InvalidDataException("Invalid tile key '{0}' on template '{1}' of tileset '{2}'.".F(node.Key, Id, tileSet.Id));
|
||||
if (!int.TryParse(node.Key, out var key))
|
||||
throw new YamlException("Tileset `{0}` template `{1}` defines a frame `{2}` that is not a valid integer.".F(tileSet.Id, Id, node.Key));
|
||||
|
||||
if (key < 0 || key >= tileInfo.Length)
|
||||
throw new YamlException("Tileset `{0}` template `{1}` references frame {2}, but only [0..{3}] are valid for a {4}x{5} Size template.".F(tileSet.Id, Id, key, tileInfo.Length - 1, Size.X, Size.Y));
|
||||
|
||||
tileInfo[key] = LoadTileInfo(tileSet, node.Value);
|
||||
}
|
||||
@@ -87,9 +81,11 @@ namespace OpenRA
|
||||
var i = 0;
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
int key;
|
||||
if (!int.TryParse(node.Key, out key) || key != i++)
|
||||
throw new InvalidDataException("Invalid tile key '{0}' on template '{1}' of tileset '{2}'.".F(node.Key, Id, tileSet.Id));
|
||||
if (!int.TryParse(node.Key, out var key))
|
||||
throw new YamlException("Tileset `{0}` template `{1}` defines a frame `{2}` that is not a valid integer.".F(tileSet.Id, Id, node.Key));
|
||||
|
||||
if (key != i++)
|
||||
throw new YamlException("Tileset `{0}` template `{1}` is missing a definition for frame {2}.".F(tileSet.Id, Id, i - 1));
|
||||
|
||||
tileInfo[key] = LoadTileInfo(tileSet, node.Value);
|
||||
}
|
||||
@@ -106,11 +102,11 @@ namespace OpenRA
|
||||
|
||||
// Fall back to the terrain-type color if necessary
|
||||
var overrideColor = tileSet.TerrainInfo[tile.TerrainType].Color;
|
||||
if (tile.LeftColor == default(Color))
|
||||
tile.GetType().GetField("LeftColor").SetValue(tile, overrideColor);
|
||||
if (tile.MinColor == default(Color))
|
||||
tile.GetType().GetField("MinColor").SetValue(tile, overrideColor);
|
||||
|
||||
if (tile.RightColor == default(Color))
|
||||
tile.GetType().GetField("RightColor").SetValue(tile, overrideColor);
|
||||
if (tile.MaxColor == default(Color))
|
||||
tile.GetType().GetField("MaxColor").SetValue(tile, overrideColor);
|
||||
|
||||
return tile;
|
||||
}
|
||||
@@ -139,6 +135,8 @@ namespace OpenRA
|
||||
public readonly string[] EditorTemplateOrder;
|
||||
public readonly bool IgnoreTileSpriteOffsets;
|
||||
public readonly bool EnableDepth = false;
|
||||
public readonly float MinHeightColorBrightness = 1.0f;
|
||||
public readonly float MaxHeightColorBrightness = 1.0f;
|
||||
|
||||
[FieldLoader.Ignore]
|
||||
public readonly IReadOnlyDictionary<ushort, TerrainTemplateInfo> Templates;
|
||||
@@ -163,14 +161,14 @@ namespace OpenRA
|
||||
.ToArray();
|
||||
|
||||
if (TerrainInfo.Length >= byte.MaxValue)
|
||||
throw new InvalidDataException("Too many terrain types.");
|
||||
throw new YamlException("Too many terrain types.");
|
||||
|
||||
for (byte i = 0; i < TerrainInfo.Length; i++)
|
||||
{
|
||||
var tt = TerrainInfo[i].Type;
|
||||
|
||||
if (terrainIndexByType.ContainsKey(tt))
|
||||
throw new InvalidDataException("Duplicate terrain type '{0}' in '{1}'.".F(tt, filepath));
|
||||
throw new YamlException("Duplicate terrain type '{0}' in '{1}'.".F(tt, filepath));
|
||||
|
||||
terrainIndexByType.Add(tt, i);
|
||||
}
|
||||
@@ -215,8 +213,7 @@ namespace OpenRA
|
||||
|
||||
public byte GetTerrainIndex(string type)
|
||||
{
|
||||
byte index;
|
||||
if (terrainIndexByType.TryGetValue(type, out index))
|
||||
if (terrainIndexByType.TryGetValue(type, out var index))
|
||||
return index;
|
||||
|
||||
throw new InvalidDataException("Tileset '{0}' lacks terrain type '{1}'".F(Id, type));
|
||||
@@ -224,8 +221,7 @@ namespace OpenRA
|
||||
|
||||
public byte GetTerrainIndex(TerrainTile r)
|
||||
{
|
||||
TerrainTemplateInfo tpl;
|
||||
if (!Templates.TryGetValue(r.Type, out tpl))
|
||||
if (!Templates.TryGetValue(r.Type, out var tpl))
|
||||
return defaultWalkableTerrainIndex;
|
||||
|
||||
if (tpl.Contains(r.Index))
|
||||
@@ -240,8 +236,7 @@ namespace OpenRA
|
||||
|
||||
public TerrainTileInfo GetTileInfo(TerrainTile r)
|
||||
{
|
||||
TerrainTemplateInfo tpl;
|
||||
if (!Templates.TryGetValue(r.Type, out tpl))
|
||||
if (!Templates.TryGetValue(r.Type, out var tpl))
|
||||
return null;
|
||||
|
||||
return tpl.Contains(r.Index) ? tpl[r.Index] : null;
|
||||
|
||||
@@ -99,7 +99,10 @@ namespace OpenRA
|
||||
|
||||
public MiniYaml Clone()
|
||||
{
|
||||
return new MiniYaml(Value, Nodes.Select(n => n.Clone()).ToList());
|
||||
var clonedNodes = new MiniYamlNodes(Nodes.Count);
|
||||
foreach (var node in Nodes)
|
||||
clonedNodes.Add(node.Clone());
|
||||
return new MiniYaml(Value, clonedNodes);
|
||||
}
|
||||
|
||||
public Dictionary<string, MiniYaml> ToDictionary()
|
||||
@@ -148,8 +151,11 @@ namespace OpenRA
|
||||
return nd.ContainsKey(s) ? nd[s].Nodes : new List<MiniYamlNode>();
|
||||
}
|
||||
|
||||
static List<MiniYamlNode> FromLines(IEnumerable<string> lines, string filename, bool discardCommentsAndWhitespace)
|
||||
static List<MiniYamlNode> FromLines(IEnumerable<string> lines, string filename, bool discardCommentsAndWhitespace, Dictionary<string, string> stringPool)
|
||||
{
|
||||
if (stringPool == null)
|
||||
stringPool = new Dictionary<string, string>();
|
||||
|
||||
var levels = new List<List<MiniYamlNode>>();
|
||||
levels.Add(new List<MiniYamlNode>());
|
||||
|
||||
@@ -263,6 +269,10 @@ namespace OpenRA
|
||||
|
||||
if (key != null || !discardCommentsAndWhitespace)
|
||||
{
|
||||
key = key == null ? null : stringPool.GetOrAdd(key, key);
|
||||
value = value == null ? null : stringPool.GetOrAdd(value, value);
|
||||
comment = comment == null ? null : stringPool.GetOrAdd(comment, comment);
|
||||
|
||||
var nodes = new List<MiniYamlNode>();
|
||||
levels[level].Add(new MiniYamlNode(key, value, comment, nodes, location));
|
||||
|
||||
@@ -270,23 +280,33 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var nodes in levels)
|
||||
nodes.TrimExcess();
|
||||
|
||||
return levels[0];
|
||||
}
|
||||
|
||||
public static List<MiniYamlNode> FromFile(string path, bool discardCommentsAndWhitespace = true)
|
||||
public static List<MiniYamlNode> FromFile(string path, bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
|
||||
{
|
||||
return FromLines(File.ReadAllLines(path), path, discardCommentsAndWhitespace);
|
||||
return FromLines(File.ReadAllLines(path), path, discardCommentsAndWhitespace, stringPool);
|
||||
}
|
||||
|
||||
public static List<MiniYamlNode> FromStream(Stream s, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true)
|
||||
public static List<MiniYamlNode> FromStream(Stream s, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
|
||||
{
|
||||
IEnumerable<string> Lines(StreamReader reader)
|
||||
{
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
yield return line;
|
||||
}
|
||||
|
||||
using (var reader = new StreamReader(s))
|
||||
return FromString(reader.ReadToEnd(), fileName, discardCommentsAndWhitespace);
|
||||
return FromLines(Lines(reader), fileName, discardCommentsAndWhitespace, stringPool);
|
||||
}
|
||||
|
||||
public static List<MiniYamlNode> FromString(string text, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true)
|
||||
public static List<MiniYamlNode> FromString(string text, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
|
||||
{
|
||||
return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None), fileName, discardCommentsAndWhitespace);
|
||||
return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None), fileName, discardCommentsAndWhitespace, stringPool);
|
||||
}
|
||||
|
||||
public static List<MiniYamlNode> Merge(IEnumerable<List<MiniYamlNode>> sources)
|
||||
@@ -330,7 +350,7 @@ namespace OpenRA
|
||||
|
||||
static List<MiniYamlNode> ResolveInherits(string key, MiniYaml node, Dictionary<string, MiniYaml> tree, Dictionary<string, MiniYamlNode.SourceLocation> inherited)
|
||||
{
|
||||
var resolved = new List<MiniYamlNode>();
|
||||
var resolved = new List<MiniYamlNode>(node.Nodes.Count);
|
||||
|
||||
// Inheritance is tracked from parent->child, but not from child->parentsiblings.
|
||||
inherited = new Dictionary<string, MiniYamlNode.SourceLocation>(inherited);
|
||||
@@ -339,8 +359,7 @@ namespace OpenRA
|
||||
{
|
||||
if (n.Key == "Inherits" || n.Key.StartsWith("Inherits@", StringComparison.Ordinal))
|
||||
{
|
||||
MiniYaml parent;
|
||||
if (!tree.TryGetValue(n.Value.Value, out parent))
|
||||
if (!tree.TryGetValue(n.Value.Value, out var parent))
|
||||
throw new YamlException(
|
||||
"{0}: Parent type `{1}` not found".F(n.Location, n.Value.Value));
|
||||
|
||||
@@ -362,6 +381,7 @@ namespace OpenRA
|
||||
MergeIntoResolved(n, resolved, tree, inherited);
|
||||
}
|
||||
|
||||
resolved.TrimExcess();
|
||||
return resolved;
|
||||
}
|
||||
|
||||
@@ -398,6 +418,7 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
ret.TrimExcess();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -428,9 +449,8 @@ namespace OpenRA
|
||||
|
||||
foreach (var key in allKeys)
|
||||
{
|
||||
MiniYamlNode existingNode, overrideNode;
|
||||
existingDict.TryGetValue(key, out existingNode);
|
||||
overrideDict.TryGetValue(key, out overrideNode);
|
||||
existingDict.TryGetValue(key, out var existingNode);
|
||||
overrideDict.TryGetValue(key, out var overrideNode);
|
||||
|
||||
var loc = overrideNode == null ? default(MiniYamlNode.SourceLocation) : overrideNode.Location;
|
||||
var comment = (overrideNode ?? existingNode).Comment;
|
||||
@@ -439,6 +459,7 @@ namespace OpenRA
|
||||
ret.Add(merged);
|
||||
}
|
||||
|
||||
ret.TrimExcess();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,6 @@ namespace OpenRA
|
||||
throw new InvalidOperationException("Unable to find a sequence loader for type '{0}'.".F(sequenceFormat.Type));
|
||||
|
||||
SpriteSequenceLoader = (ISpriteSequenceLoader)sequenceCtor.Invoke(new[] { this });
|
||||
SpriteSequenceLoader.OnMissingSpriteError = s => Log.Write("debug", s);
|
||||
|
||||
var modelFormat = Manifest.Get<ModelSequenceFormat>();
|
||||
var modelLoader = ObjectCreator.FindType(modelFormat.Type + "Loader");
|
||||
@@ -108,7 +107,7 @@ namespace OpenRA
|
||||
|
||||
defaultSequences = Exts.Lazy(() =>
|
||||
{
|
||||
var items = DefaultTileSets.ToDictionary(t => t.Key, t => new SequenceProvider(DefaultFileSystem, this, t.Value, null));
|
||||
var items = DefaultTileSets.ToDictionary(t => t.Key, t => new SequenceProvider(DefaultFileSystem, this, t.Key, null));
|
||||
return (IReadOnlyDictionary<string, SequenceProvider>)(new ReadOnlyDictionary<string, SequenceProvider>(items));
|
||||
});
|
||||
|
||||
@@ -177,8 +176,7 @@ namespace OpenRA
|
||||
|
||||
public Map PrepareMap(string uid)
|
||||
{
|
||||
if (LoadScreen != null)
|
||||
LoadScreen.Display();
|
||||
LoadScreen?.Display();
|
||||
|
||||
if (MapCache[uid].Status != MapStatus.Available)
|
||||
throw new InvalidDataException("Invalid map uid: {0}".F(uid));
|
||||
@@ -202,12 +200,12 @@ namespace OpenRA
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (LoadScreen != null)
|
||||
LoadScreen.Dispose();
|
||||
LoadScreen?.Dispose();
|
||||
MapCache.Dispose();
|
||||
|
||||
if (ObjectCreator != null)
|
||||
ObjectCreator.Dispose();
|
||||
ObjectCreator?.Dispose();
|
||||
|
||||
Manifest.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using OpenRA.Server;
|
||||
@@ -30,12 +33,63 @@ namespace OpenRA.Network
|
||||
{
|
||||
int LocalClientId { get; }
|
||||
ConnectionState ConnectionState { get; }
|
||||
IPEndPoint EndPoint { get; }
|
||||
string ErrorMessage { get; }
|
||||
void Send(int frame, List<byte[]> orders);
|
||||
void SendImmediate(IEnumerable<byte[]> orders);
|
||||
void SendSync(int frame, byte[] syncData);
|
||||
void Receive(Action<int, byte[]> packetFn);
|
||||
}
|
||||
|
||||
public class ConnectionTarget
|
||||
{
|
||||
readonly DnsEndPoint[] endpoints;
|
||||
|
||||
public ConnectionTarget()
|
||||
{
|
||||
endpoints = new[] { new DnsEndPoint("invalid", 0) };
|
||||
}
|
||||
|
||||
public ConnectionTarget(string host, int port)
|
||||
{
|
||||
endpoints = new[] { new DnsEndPoint(host, port) };
|
||||
}
|
||||
|
||||
public ConnectionTarget(IEnumerable<DnsEndPoint> endpoints)
|
||||
{
|
||||
this.endpoints = endpoints.ToArray();
|
||||
if (this.endpoints.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("ConnectionTarget must have at least one address.");
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IPEndPoint> GetConnectEndPoints()
|
||||
{
|
||||
return endpoints
|
||||
.SelectMany(e =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return Dns.GetHostAddresses(e.Host)
|
||||
.Select(a => new IPEndPoint(a, e.Port));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return Enumerable.Empty<IPEndPoint>();
|
||||
}
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return endpoints
|
||||
.Select(e => "{0}:{1}".F(e.Host, e.Port))
|
||||
.JoinWith("/");
|
||||
}
|
||||
}
|
||||
|
||||
class EchoConnection : IConnection
|
||||
{
|
||||
protected struct ReceivedPacket
|
||||
@@ -57,6 +111,16 @@ namespace OpenRA.Network
|
||||
get { return ConnectionState.PreConnecting; }
|
||||
}
|
||||
|
||||
public virtual IPEndPoint EndPoint
|
||||
{
|
||||
get { throw new NotSupportedException("An echo connection doesn't have an endpoint"); }
|
||||
}
|
||||
|
||||
public virtual string ErrorMessage
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public virtual void Send(int frame, List<byte[]> orders)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
@@ -110,23 +174,21 @@ namespace OpenRA.Network
|
||||
foreach (var p in packets)
|
||||
{
|
||||
packetFn(p.FromClient, p.Data);
|
||||
if (Recorder != null)
|
||||
Recorder.Receive(p.FromClient, p.Data);
|
||||
Recorder?.Receive(p.FromClient, p.Data);
|
||||
}
|
||||
}
|
||||
|
||||
public void StartRecording(Func<string> chooseFilename)
|
||||
{
|
||||
// If we have a previous recording then save/dispose it and start a new one.
|
||||
if (Recorder != null)
|
||||
Recorder.Dispose();
|
||||
Recorder?.Dispose();
|
||||
Recorder = new ReplayRecorder(chooseFilename);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && Recorder != null)
|
||||
Recorder.Dispose();
|
||||
if (disposing)
|
||||
Recorder?.Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -138,35 +200,100 @@ namespace OpenRA.Network
|
||||
|
||||
sealed class NetworkConnection : EchoConnection
|
||||
{
|
||||
readonly TcpClient tcp;
|
||||
readonly ConnectionTarget target;
|
||||
TcpClient tcp;
|
||||
IPEndPoint endpoint;
|
||||
readonly List<byte[]> queuedSyncPackets = new List<byte[]>();
|
||||
volatile ConnectionState connectionState = ConnectionState.Connecting;
|
||||
volatile int clientId;
|
||||
bool disposed;
|
||||
string errorMessage;
|
||||
|
||||
public NetworkConnection(string host, int port)
|
||||
public override IPEndPoint EndPoint { get { return endpoint; } }
|
||||
|
||||
public override string ErrorMessage { get { return errorMessage; } }
|
||||
|
||||
public NetworkConnection(ConnectionTarget target)
|
||||
{
|
||||
try
|
||||
this.target = target;
|
||||
new Thread(NetworkConnectionConnect)
|
||||
{
|
||||
tcp = new TcpClient(host, port) { NoDelay = true };
|
||||
Name = "{0} (connect to {1})".F(GetType().Name, target),
|
||||
IsBackground = true
|
||||
}.Start();
|
||||
}
|
||||
|
||||
void NetworkConnectionConnect()
|
||||
{
|
||||
var queue = new BlockingCollection<TcpClient>();
|
||||
|
||||
var atLeastOneEndpoint = false;
|
||||
foreach (var endpoint in target.GetConnectEndPoints())
|
||||
{
|
||||
atLeastOneEndpoint = true;
|
||||
new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = new TcpClient(endpoint.AddressFamily) { NoDelay = true };
|
||||
client.Connect(endpoint.Address, endpoint.Port);
|
||||
|
||||
try
|
||||
{
|
||||
queue.Add(client);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Another connection was faster, close this one.
|
||||
client.Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = "Failed to connect";
|
||||
Log.Write("client", "Failed to connect to {0}: {1}".F(endpoint, ex.Message));
|
||||
}
|
||||
})
|
||||
{
|
||||
Name = "{0} (connect to {1})".F(GetType().Name, endpoint),
|
||||
IsBackground = true
|
||||
}.Start();
|
||||
}
|
||||
|
||||
if (!atLeastOneEndpoint)
|
||||
{
|
||||
errorMessage = "Failed to resolve address";
|
||||
connectionState = ConnectionState.NotConnected;
|
||||
}
|
||||
|
||||
// Wait up to 5s for a successful connection. This should hopefully be enough because such high latency makes the game unplayable anyway.
|
||||
else if (queue.TryTake(out tcp, 5000))
|
||||
{
|
||||
// Copy endpoint here to have it even after getting disconnected.
|
||||
endpoint = (IPEndPoint)tcp.Client.RemoteEndPoint;
|
||||
|
||||
new Thread(NetworkConnectionReceive)
|
||||
{
|
||||
Name = GetType().Name + " " + host + ":" + port,
|
||||
Name = "{0} (receive from {1})".F(GetType().Name, tcp.Client.RemoteEndPoint),
|
||||
IsBackground = true
|
||||
}.Start(tcp.GetStream());
|
||||
}.Start();
|
||||
}
|
||||
catch
|
||||
else
|
||||
{
|
||||
connectionState = ConnectionState.NotConnected;
|
||||
}
|
||||
|
||||
// Close all unneeded connections in the queue and make sure new ones are closed on the connect thread.
|
||||
queue.CompleteAdding();
|
||||
foreach (var client in queue)
|
||||
client.Close();
|
||||
}
|
||||
|
||||
void NetworkConnectionReceive(object networkStreamObject)
|
||||
void NetworkConnectionReceive()
|
||||
{
|
||||
try
|
||||
{
|
||||
var networkStream = (NetworkStream)networkStreamObject;
|
||||
var reader = new BinaryReader(networkStream);
|
||||
var reader = new BinaryReader(tcp.GetStream());
|
||||
var handshakeProtocol = reader.ReadInt32();
|
||||
|
||||
if (handshakeProtocol != ProtocolVersion.Handshake)
|
||||
@@ -187,7 +314,11 @@ namespace OpenRA.Network
|
||||
AddPacket(new ReceivedPacket { FromClient = client, Data = buf });
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = "Connection failed";
|
||||
Log.Write("client", "Connection to {0} failed: {1}".F(endpoint, ex.Message));
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectionState = ConnectionState.NotConnected;
|
||||
@@ -239,8 +370,7 @@ namespace OpenRA.Network
|
||||
|
||||
// Closing the stream will cause any reads on the receiving thread to throw.
|
||||
// This will mark the connection as no longer connected and the thread will terminate cleanly.
|
||||
if (tcp != null)
|
||||
tcp.Close();
|
||||
tcp?.Close();
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace OpenRA.Network
|
||||
if (lastClientFrame == -1)
|
||||
lastClientFrame = framePackets
|
||||
.Where(x => x.Value.ContainsKey(clientId))
|
||||
.Select(x => x.Key).OrderBy(x => x).LastOrDefault();
|
||||
.Select(x => x.Key).MaxByOrDefault(x => x);
|
||||
|
||||
clientQuitTimes[clientId] = lastClientFrame;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace OpenRA.Network
|
||||
public readonly string Faction;
|
||||
public readonly int SpawnPoint;
|
||||
public readonly int Team;
|
||||
public readonly int Handicap;
|
||||
public readonly string Slot;
|
||||
public readonly string Bot;
|
||||
public readonly bool IsAdmin;
|
||||
@@ -39,6 +40,7 @@ namespace OpenRA.Network
|
||||
Faction = client.Faction;
|
||||
SpawnPoint = client.SpawnPoint;
|
||||
Team = client.Team;
|
||||
Handicap = client.Handicap;
|
||||
Slot = client.Slot;
|
||||
Bot = client.Bot;
|
||||
IsAdmin = client.IsAdmin;
|
||||
@@ -53,6 +55,7 @@ namespace OpenRA.Network
|
||||
client.Faction = Faction;
|
||||
client.SpawnPoint = SpawnPoint;
|
||||
client.Team = Team;
|
||||
client.Handicap = Handicap;
|
||||
client.Slot = Slot;
|
||||
client.Bot = Bot;
|
||||
client.IsAdmin = IsAdmin;
|
||||
@@ -117,7 +120,7 @@ namespace OpenRA.Network
|
||||
|
||||
LastOrdersFrame = rs.ReadInt32();
|
||||
LastSyncFrame = rs.ReadInt32();
|
||||
lastSyncPacket = rs.ReadBytes(5);
|
||||
lastSyncPacket = rs.ReadBytes(Order.SyncHashOrderLength);
|
||||
|
||||
var globalSettings = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength));
|
||||
GlobalSettings = Session.Global.Deserialize(globalSettings[0].Value);
|
||||
@@ -190,6 +193,12 @@ namespace OpenRA.Network
|
||||
// Sync packet - we only care about the last value
|
||||
if (data.Length > 0 && data[0] == (byte)OrderType.SyncHash && frame > LastSyncFrame)
|
||||
{
|
||||
if (data.Length != Order.SyncHashOrderLength)
|
||||
{
|
||||
Log.Write("debug", "Dropped sync order with length {0}. Expected length {1}.".F(data.Length, Order.SyncHashOrderLength));
|
||||
return;
|
||||
}
|
||||
|
||||
LastSyncFrame = frame;
|
||||
lastSyncPacket = data;
|
||||
}
|
||||
@@ -282,7 +291,7 @@ namespace OpenRA.Network
|
||||
file.Write(BitConverter.GetBytes(MetadataMarker), 0, 4);
|
||||
file.Write(BitConverter.GetBytes(LastOrdersFrame), 0, 4);
|
||||
file.Write(BitConverter.GetBytes(LastSyncFrame), 0, 4);
|
||||
file.Write(lastSyncPacket, 0, 5);
|
||||
file.Write(lastSyncPacket, 0, Order.SyncHashOrderLength);
|
||||
|
||||
var globalSettingsNodes = new List<MiniYamlNode>() { GlobalSettings.Serialize() };
|
||||
file.WriteString(Encoding.UTF8, globalSettingsNodes.WriteToString());
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace OpenRA.Network
|
||||
"Mod", "Version", "ModTitle", "ModWebsite", "ModIcon32",
|
||||
|
||||
// Current server state
|
||||
"Map", "State", "MaxPlayers", "Protected", "Authentication"
|
||||
"Map", "State", "MaxPlayers", "Protected", "Authentication", "DisabledSpawnPoints"
|
||||
};
|
||||
|
||||
public const int ProtocolVersion = 2;
|
||||
@@ -132,6 +132,9 @@ namespace OpenRA.Network
|
||||
[FieldLoader.LoadUsing("LoadClients")]
|
||||
public readonly GameClient[] Clients;
|
||||
|
||||
/// <summary>The list of spawnpoints that are disabled for this game</summary>
|
||||
public readonly int[] DisabledSpawnPoints = { };
|
||||
|
||||
public string ModLabel { get { return "{0} ({1})".F(ModTitle, Version); } }
|
||||
|
||||
static object LoadClients(MiniYaml yaml)
|
||||
@@ -167,28 +170,22 @@ namespace OpenRA.Network
|
||||
|
||||
// Games advertised using the old API calculated the play time locally
|
||||
if (State == 2 && PlayTime < 0)
|
||||
{
|
||||
DateTime startTime;
|
||||
if (DateTime.TryParse(Started, out startTime))
|
||||
if (DateTime.TryParse(Started, out var startTime))
|
||||
PlayTime = (int)(DateTime.UtcNow - startTime).TotalSeconds;
|
||||
}
|
||||
|
||||
ExternalMod external;
|
||||
var externalKey = ExternalMod.MakeKey(Mod, Version);
|
||||
if (Game.ExternalMods.TryGetValue(externalKey, out external) && external.Version == Version)
|
||||
if (Game.ExternalMods.TryGetValue(externalKey, out var external) && external.Version == Version)
|
||||
IsCompatible = true;
|
||||
|
||||
// Games advertised using the old API used local mod metadata
|
||||
if (string.IsNullOrEmpty(ModTitle))
|
||||
{
|
||||
Manifest mod;
|
||||
|
||||
if (external != null && external.Version == Version)
|
||||
{
|
||||
// Use external mod registration to populate the section header
|
||||
ModTitle = external.Title;
|
||||
}
|
||||
else if (Game.Mods.TryGetValue(Mod, out mod))
|
||||
else if (Game.Mods.TryGetValue(Mod, out var mod))
|
||||
{
|
||||
// Use internal mod data to populate the section header, but
|
||||
// on-connect switching must use the external mod plumbing.
|
||||
@@ -232,6 +229,7 @@ namespace OpenRA.Network
|
||||
Protected = !string.IsNullOrEmpty(server.Settings.Password);
|
||||
Authentication = server.Settings.RequireAuthentication || server.Settings.ProfileIDWhitelist.Any();
|
||||
Clients = server.LobbyInfo.Clients.Select(c => new GameClient(c)).ToArray();
|
||||
DisabledSpawnPoints = server.LobbyInfo.DisabledSpawnPoints?.ToArray() ?? Array.Empty<int>();
|
||||
}
|
||||
|
||||
public string ToPOSTData(bool lanGame)
|
||||
|
||||
@@ -11,59 +11,120 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using ICSharpCode.SharpZipLib.GZip;
|
||||
using MaxMind.Db;
|
||||
using System.Net.Sockets;
|
||||
using System.Numerics;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
|
||||
namespace OpenRA.Network
|
||||
{
|
||||
public class GeoIP
|
||||
{
|
||||
public class GeoIP2Record
|
||||
class IP2LocationReader
|
||||
{
|
||||
[Constructor]
|
||||
public GeoIP2Record(GeoIP2Country country)
|
||||
public readonly DateTime Date;
|
||||
readonly Stream stream;
|
||||
readonly uint columnCount;
|
||||
readonly uint v4Count;
|
||||
readonly uint v4Offset;
|
||||
readonly uint v6Count;
|
||||
readonly uint v6Offset;
|
||||
|
||||
public IP2LocationReader(Stream source)
|
||||
{
|
||||
Country = country;
|
||||
// Copy stream data for reuse
|
||||
stream = new MemoryStream();
|
||||
source.CopyTo(stream);
|
||||
stream.Position = 0;
|
||||
|
||||
if (stream.ReadUInt8() != 1)
|
||||
throw new InvalidDataException("Only IP2Location type 1 databases are supported.");
|
||||
|
||||
columnCount = stream.ReadUInt8();
|
||||
var year = stream.ReadUInt8();
|
||||
var month = stream.ReadUInt8();
|
||||
var day = stream.ReadUInt8();
|
||||
Date = new DateTime(2000 + year, month, day);
|
||||
|
||||
v4Count = stream.ReadUInt32();
|
||||
v4Offset = stream.ReadUInt32();
|
||||
v6Count = stream.ReadUInt32();
|
||||
v6Offset = stream.ReadUInt32();
|
||||
}
|
||||
|
||||
public GeoIP2Country Country { get; set; }
|
||||
}
|
||||
|
||||
public class GeoIP2Country
|
||||
{
|
||||
[Constructor]
|
||||
public GeoIP2Country(GeoIP2CountryNames names)
|
||||
BigInteger AddressForIndex(long index, bool isIPv6)
|
||||
{
|
||||
Names = names;
|
||||
var start = isIPv6 ? v6Offset : v4Offset;
|
||||
var offset = isIPv6 ? 12 : 0;
|
||||
stream.Seek(start + index * (4 * columnCount + offset) - 1, SeekOrigin.Begin);
|
||||
return new BigInteger(stream.ReadBytes(isIPv6 ? 16 : 4).Append((byte)0).ToArray());
|
||||
}
|
||||
|
||||
public GeoIP2CountryNames Names { get; set; }
|
||||
}
|
||||
|
||||
public class GeoIP2CountryNames
|
||||
{
|
||||
[Constructor]
|
||||
public GeoIP2CountryNames(string en)
|
||||
string CountryForIndex(long index, bool isIPv6)
|
||||
{
|
||||
English = en;
|
||||
// Read file offset for country entry
|
||||
var start = isIPv6 ? v6Offset : v4Offset;
|
||||
var offset = isIPv6 ? 12 : 0;
|
||||
stream.Seek(start + index * (4 * columnCount + offset) + offset + 3, SeekOrigin.Begin);
|
||||
var countryOffset = stream.ReadUInt32();
|
||||
|
||||
// Read length-prefixed country name
|
||||
stream.Seek(countryOffset + 3, SeekOrigin.Begin);
|
||||
var length = stream.ReadUInt8();
|
||||
|
||||
// "-" is used to represent an unknown country in the database
|
||||
var country = stream.ReadASCII(length);
|
||||
return country != "-" ? country : null;
|
||||
}
|
||||
|
||||
public string English { get; set; }
|
||||
public string LookupCountry(IPAddress ip)
|
||||
{
|
||||
var isIPv6 = ip.AddressFamily == AddressFamily.InterNetworkV6;
|
||||
if (!isIPv6 && ip.AddressFamily != AddressFamily.InterNetwork)
|
||||
return null;
|
||||
|
||||
// Locate IP using a binary search
|
||||
// The IP2Location python parser has an additional
|
||||
// optimization that can jump directly to the row, but this adds
|
||||
// extra complexity that isn't obviously needed for our limited database size
|
||||
long low = 0;
|
||||
long high = isIPv6 ? v6Count : v4Count;
|
||||
|
||||
// Append an empty byte to force the data to be treated as unsigned
|
||||
var ipNumber = new BigInteger(ip.GetAddressBytes().Reverse().Append((byte)0).ToArray());
|
||||
while (low <= high)
|
||||
{
|
||||
var mid = (low + high) / 2;
|
||||
var min = AddressForIndex(mid, isIPv6);
|
||||
var max = AddressForIndex(mid + 1, isIPv6);
|
||||
if (min <= ipNumber && ipNumber < max)
|
||||
return CountryForIndex(mid, isIPv6);
|
||||
|
||||
if (ipNumber < min)
|
||||
high = mid - 1;
|
||||
else
|
||||
low = mid + 1;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Reader database;
|
||||
static IP2LocationReader database;
|
||||
|
||||
public static void Initialize(string databasePath)
|
||||
public static void Initialize(string databasePath = "IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP")
|
||||
{
|
||||
if (string.IsNullOrEmpty(databasePath))
|
||||
if (!File.Exists(databasePath))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
using (var fileStream = new FileStream(databasePath, FileMode.Open, FileAccess.Read))
|
||||
using (var gzipStream = new GZipInputStream(fileStream))
|
||||
database = new Reader(gzipStream);
|
||||
using (var z = new ZipFile(databasePath))
|
||||
{
|
||||
var entry = z.FindEntry("IP2LOCATION-LITE-DB1.IPV6.BIN", false);
|
||||
database = new IP2LocationReader(z.GetInputStream(entry));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -77,9 +138,7 @@ namespace OpenRA.Network
|
||||
{
|
||||
try
|
||||
{
|
||||
var record = database.Find<GeoIP2Record>(ip);
|
||||
if (record != null)
|
||||
return record.Country.Names.English;
|
||||
return database.LookupCountry(ip);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum OrderFields : byte
|
||||
enum OrderFields : short
|
||||
{
|
||||
None = 0x0,
|
||||
Target = 0x01,
|
||||
@@ -35,7 +35,8 @@ namespace OpenRA
|
||||
ExtraLocation = 0x10,
|
||||
ExtraData = 0x20,
|
||||
TargetIsCell = 0x40,
|
||||
Subject = 0x80
|
||||
Subject = 0x80,
|
||||
Grouped = 0x100
|
||||
}
|
||||
|
||||
static class OrderFieldsExts
|
||||
@@ -48,10 +49,15 @@ namespace OpenRA
|
||||
|
||||
public sealed class Order
|
||||
{
|
||||
// Length of orders with type OrderType.SyncHash
|
||||
public const int SyncHashOrderLength = 13;
|
||||
|
||||
public readonly string OrderString;
|
||||
public readonly Actor Subject;
|
||||
public readonly bool Queued;
|
||||
public readonly Target Target;
|
||||
public ref readonly Target Target => ref target;
|
||||
public readonly Actor[] GroupedActors;
|
||||
|
||||
public string TargetString;
|
||||
public CPos ExtraLocation;
|
||||
public Actor[] ExtraActors;
|
||||
@@ -60,20 +66,24 @@ namespace OpenRA
|
||||
public OrderType Type = OrderType.Fields;
|
||||
|
||||
public bool SuppressVisualFeedback;
|
||||
public Target VisualFeedbackTarget;
|
||||
public ref readonly Target VisualFeedbackTarget => ref visualFeedbackTarget;
|
||||
|
||||
public Player Player { get { return Subject != null ? Subject.Owner : null; } }
|
||||
|
||||
Order(string orderString, Actor subject, Target target, string targetString, bool queued, Actor[] extraActors, CPos extraLocation, uint extraData)
|
||||
readonly Target target;
|
||||
readonly Target visualFeedbackTarget;
|
||||
|
||||
Order(string orderString, Actor subject, in Target target, string targetString, bool queued, Actor[] extraActors, CPos extraLocation, uint extraData, Actor[] groupedActors = null)
|
||||
{
|
||||
OrderString = orderString ?? "";
|
||||
Subject = subject;
|
||||
Target = target;
|
||||
this.target = target;
|
||||
TargetString = targetString;
|
||||
Queued = queued;
|
||||
ExtraActors = extraActors;
|
||||
ExtraLocation = extraLocation;
|
||||
ExtraData = extraData;
|
||||
GroupedActors = groupedActors;
|
||||
}
|
||||
|
||||
public static Order Deserialize(World world, BinaryReader r)
|
||||
@@ -86,7 +96,7 @@ namespace OpenRA
|
||||
case OrderType.Fields:
|
||||
{
|
||||
var order = r.ReadString();
|
||||
var flags = (OrderFields)r.ReadByte();
|
||||
var flags = (OrderFields)r.ReadInt16();
|
||||
|
||||
Actor subject = null;
|
||||
if (flags.HasField(OrderFields.Subject))
|
||||
@@ -103,8 +113,7 @@ namespace OpenRA
|
||||
{
|
||||
case TargetType.Actor:
|
||||
{
|
||||
Actor targetActor;
|
||||
if (world != null && TryGetActorFromUInt(world, r.ReadUInt32(), out targetActor))
|
||||
if (world != null && TryGetActorFromUInt(world, r.ReadUInt32(), out var targetActor))
|
||||
target = Target.FromActor(targetActor);
|
||||
break;
|
||||
}
|
||||
@@ -114,8 +123,7 @@ namespace OpenRA
|
||||
var playerActorID = r.ReadUInt32();
|
||||
var frozenActorID = r.ReadUInt32();
|
||||
|
||||
Actor playerActor;
|
||||
if (world == null || !TryGetActorFromUInt(world, playerActorID, out playerActor))
|
||||
if (world == null || !TryGetActorFromUInt(world, playerActorID, out var playerActor))
|
||||
break;
|
||||
|
||||
if (playerActor.Owner.FrozenActorLayer == null)
|
||||
@@ -164,13 +172,23 @@ namespace OpenRA
|
||||
var extraLocation = flags.HasField(OrderFields.ExtraLocation) ? new CPos(r.ReadInt32()) : CPos.Zero;
|
||||
var extraData = flags.HasField(OrderFields.ExtraData) ? r.ReadUInt32() : 0;
|
||||
|
||||
Actor[] groupedActors = null;
|
||||
if (flags.HasField(OrderFields.Grouped))
|
||||
{
|
||||
var count = r.ReadInt32();
|
||||
if (world != null)
|
||||
groupedActors = Exts.MakeArray(count, _ => world.GetActorById(r.ReadUInt32()));
|
||||
else
|
||||
r.ReadBytes(4 * count);
|
||||
}
|
||||
|
||||
if (world == null)
|
||||
return new Order(order, null, target, targetString, queued, extraActors, extraLocation, extraData);
|
||||
return new Order(order, null, target, targetString, queued, extraActors, extraLocation, extraData, groupedActors);
|
||||
|
||||
if (subject == null && flags.HasField(OrderFields.Subject))
|
||||
return null;
|
||||
|
||||
return new Order(order, subject, target, targetString, queued, extraActors, extraLocation, extraData);
|
||||
return new Order(order, subject, target, targetString, queued, extraActors, extraLocation, extraData, groupedActors);
|
||||
}
|
||||
|
||||
case OrderType.Handshake:
|
||||
@@ -231,6 +249,11 @@ namespace OpenRA
|
||||
return new Order(order, null, false) { IsImmediate = isImmediate, TargetString = targetString };
|
||||
}
|
||||
|
||||
public static Order FromGroupedOrder(Order grouped, Actor subject)
|
||||
{
|
||||
return new Order(grouped.OrderString, subject, grouped.Target, grouped.TargetString, grouped.Queued, grouped.ExtraActors, grouped.ExtraLocation, grouped.ExtraData);
|
||||
}
|
||||
|
||||
public static Order Command(string text)
|
||||
{
|
||||
return new Order("Command", null, false) { IsImmediate = true, TargetString = text };
|
||||
@@ -255,11 +278,17 @@ namespace OpenRA
|
||||
public Order()
|
||||
: this(null, null, Target.Invalid, null, false, null, CPos.Zero, 0) { }
|
||||
|
||||
public Order(string orderString, Actor subject, bool queued, Actor[] extraActors = null)
|
||||
: this(orderString, subject, Target.Invalid, null, queued, extraActors, CPos.Zero, 0) { }
|
||||
public Order(string orderString, Actor subject, bool queued, Actor[] extraActors = null, Actor[] groupedActors = null)
|
||||
: this(orderString, subject, Target.Invalid, null, queued, extraActors, CPos.Zero, 0, groupedActors) { }
|
||||
|
||||
public Order(string orderString, Actor subject, Target target, bool queued, Actor[] extraActors = null)
|
||||
: this(orderString, subject, target, null, queued, extraActors, CPos.Zero, 0) { }
|
||||
public Order(string orderString, Actor subject, in Target target, bool queued, Actor[] extraActors = null, Actor[] groupedActors = null)
|
||||
: this(orderString, subject, target, null, queued, extraActors, CPos.Zero, 0, groupedActors) { }
|
||||
|
||||
public Order(string orderString, Actor subject, Target target, Target visualFeedbackTarget, bool queued)
|
||||
: this(orderString, subject, target, null, queued, null, CPos.Zero, 0, null)
|
||||
{
|
||||
this.visualFeedbackTarget = visualFeedbackTarget;
|
||||
}
|
||||
|
||||
public byte[] Serialize()
|
||||
{
|
||||
@@ -267,7 +296,7 @@ namespace OpenRA
|
||||
if (Type == OrderType.Handshake)
|
||||
minLength += TargetString.Length + 1;
|
||||
else if (Type == OrderType.Fields)
|
||||
minLength += 4 + 1 + 13 + (TargetString != null ? TargetString.Length + 1 : 0) + 4 + 4 + 4;
|
||||
minLength += 4 + 2 + 13 + (TargetString != null ? TargetString.Length + 1 : 0) + 4 + 4 + 4;
|
||||
|
||||
if (ExtraActors != null)
|
||||
minLength += ExtraActors.Length * 4;
|
||||
@@ -308,6 +337,9 @@ namespace OpenRA
|
||||
if (Queued)
|
||||
fields |= OrderFields.Queued;
|
||||
|
||||
if (GroupedActors != null)
|
||||
fields |= OrderFields.Grouped;
|
||||
|
||||
if (ExtraActors != null)
|
||||
fields |= OrderFields.ExtraActors;
|
||||
|
||||
@@ -317,7 +349,7 @@ namespace OpenRA
|
||||
if (Target.SerializableCell != null)
|
||||
fields |= OrderFields.TargetIsCell;
|
||||
|
||||
w.Write((byte)fields);
|
||||
w.Write((short)fields);
|
||||
|
||||
if (fields.HasField(OrderFields.Subject))
|
||||
w.Write(UIntFromActor(Subject));
|
||||
@@ -362,6 +394,13 @@ namespace OpenRA
|
||||
if (fields.HasField(OrderFields.ExtraData))
|
||||
w.Write(ExtraData);
|
||||
|
||||
if (fields.HasField(OrderFields.Grouped))
|
||||
{
|
||||
w.Write(GroupedActors.Length);
|
||||
foreach (var a in GroupedActors)
|
||||
w.Write(UIntFromActor(a));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,13 +31,14 @@ namespace OpenRA.Network
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static byte[] SerializeSync(int sync)
|
||||
public static byte[] SerializeSync(int sync, ulong defeatState)
|
||||
{
|
||||
var ms = new MemoryStream(1 + 4);
|
||||
var ms = new MemoryStream(Order.SyncHashOrderLength);
|
||||
using (var writer = new BinaryWriter(ms))
|
||||
{
|
||||
writer.Write((byte)OrderType.SyncHash);
|
||||
writer.Write(sync);
|
||||
writer.Write(defeatState);
|
||||
}
|
||||
|
||||
return ms.GetBuffer();
|
||||
|
||||
@@ -28,11 +28,10 @@ namespace OpenRA.Network
|
||||
public Session.Client LocalClient { get { return LobbyInfo.ClientWithIndex(Connection.LocalClientId); } }
|
||||
public World World;
|
||||
|
||||
public readonly string Host;
|
||||
public readonly int Port;
|
||||
public readonly ConnectionTarget Endpoint;
|
||||
public readonly string Password = "";
|
||||
|
||||
public string ServerError = "Server is not responding";
|
||||
public string ServerError = null;
|
||||
public bool AuthenticationFailed = false;
|
||||
public ExternalMod ServerExternalMod = null;
|
||||
|
||||
@@ -48,10 +47,10 @@ namespace OpenRA.Network
|
||||
internal int GameSaveLastFrame = -1;
|
||||
internal int GameSaveLastSyncFrame = -1;
|
||||
|
||||
List<Order> localOrders = new List<Order>();
|
||||
List<Order> localImmediateOrders = new List<Order>();
|
||||
readonly List<Order> localOrders = new List<Order>();
|
||||
readonly List<Order> localImmediateOrders = new List<Order>();
|
||||
|
||||
List<ChatLine> chatCache = new List<ChatLine>();
|
||||
readonly List<ChatLine> chatCache = new List<ChatLine>();
|
||||
|
||||
public readonly ReadOnlyList<ChatLine> ChatCache;
|
||||
|
||||
@@ -80,10 +79,9 @@ namespace OpenRA.Network
|
||||
Connection.Send(i, new List<byte[]>());
|
||||
}
|
||||
|
||||
public OrderManager(string host, int port, string password, IConnection conn)
|
||||
public OrderManager(ConnectionTarget endpoint, string password, IConnection conn)
|
||||
{
|
||||
Host = host;
|
||||
Port = port;
|
||||
Endpoint = endpoint;
|
||||
Password = password;
|
||||
Connection = conn;
|
||||
syncReport = new SyncReport(this);
|
||||
@@ -117,7 +115,7 @@ namespace OpenRA.Network
|
||||
Connection.SendImmediate(localImmediateOrders.Select(o => o.Serialize()));
|
||||
localImmediateOrders.Clear();
|
||||
|
||||
var immediatePackets = new List<Pair<int, byte[]>>();
|
||||
var immediatePackets = new List<(int ClientId, byte[] Packet)>();
|
||||
|
||||
Connection.Receive(
|
||||
(clientId, packet) =>
|
||||
@@ -125,19 +123,27 @@ namespace OpenRA.Network
|
||||
var frame = BitConverter.ToInt32(packet, 0);
|
||||
if (packet.Length == 5 && packet[4] == (byte)OrderType.Disconnect)
|
||||
frameData.ClientQuit(clientId, frame);
|
||||
else if (packet.Length >= 5 && packet[4] == (byte)OrderType.SyncHash)
|
||||
else if (packet.Length > 4 && packet[4] == (byte)OrderType.SyncHash)
|
||||
{
|
||||
if (packet.Length != 4 + Order.SyncHashOrderLength)
|
||||
{
|
||||
Log.Write("debug", "Dropped sync order with length {0}. Expected length {1}.".F(packet.Length, 4 + Order.SyncHashOrderLength));
|
||||
return;
|
||||
}
|
||||
|
||||
CheckSync(packet);
|
||||
}
|
||||
else if (frame == 0)
|
||||
immediatePackets.Add(Pair.New(clientId, packet));
|
||||
immediatePackets.Add((clientId, packet));
|
||||
else
|
||||
frameData.AddFrameOrders(clientId, frame, packet);
|
||||
});
|
||||
|
||||
foreach (var p in immediatePackets)
|
||||
{
|
||||
foreach (var o in p.Second.ToOrderList(World))
|
||||
foreach (var o in p.Packet.ToOrderList(World))
|
||||
{
|
||||
UnitOrders.ProcessOrder(this, World, p.First, o);
|
||||
UnitOrders.ProcessOrder(this, World, p.ClientId, o);
|
||||
|
||||
// A mod switch or other event has pulled the ground from beneath us
|
||||
if (disposed)
|
||||
@@ -151,8 +157,7 @@ namespace OpenRA.Network
|
||||
void CheckSync(byte[] packet)
|
||||
{
|
||||
var frame = BitConverter.ToInt32(packet, 0);
|
||||
byte[] existingSync;
|
||||
if (syncForFrame.TryGetValue(frame, out existingSync))
|
||||
if (syncForFrame.TryGetValue(frame, out var existingSync))
|
||||
{
|
||||
if (packet.Length != existingSync.Length)
|
||||
OutOfSync(frame);
|
||||
@@ -167,14 +172,14 @@ namespace OpenRA.Network
|
||||
|
||||
public bool IsReadyForNextFrame
|
||||
{
|
||||
get { return NetFrameNumber >= 1 && frameData.IsReadyForFrame(NetFrameNumber); }
|
||||
get { return GameStarted && frameData.IsReadyForFrame(NetFrameNumber); }
|
||||
}
|
||||
|
||||
public IEnumerable<Session.Client> GetClientsNotReadyForNextFrame
|
||||
{
|
||||
get
|
||||
{
|
||||
return NetFrameNumber >= 1
|
||||
return GameStarted
|
||||
? frameData.ClientsNotReadyForFrame(NetFrameNumber)
|
||||
.Select(a => LobbyInfo.ClientWithIndex(a))
|
||||
: NoClients;
|
||||
@@ -195,9 +200,16 @@ namespace OpenRA.Network
|
||||
UnitOrders.ProcessOrder(this, World, order.Client, order.Order);
|
||||
|
||||
if (NetFrameNumber + FramesAhead >= GameSaveLastSyncFrame)
|
||||
Connection.SendSync(NetFrameNumber, OrderIO.SerializeSync(World.SyncHash()));
|
||||
{
|
||||
var defeatState = 0UL;
|
||||
for (var i = 0; i < World.Players.Length; i++)
|
||||
if (World.Players[i].WinState == WinState.Lost)
|
||||
defeatState |= 1UL << i;
|
||||
|
||||
Connection.SendSync(NetFrameNumber, OrderIO.SerializeSync(World.SyncHash(), defeatState));
|
||||
}
|
||||
else
|
||||
Connection.SendSync(NetFrameNumber, OrderIO.SerializeSync(0));
|
||||
Connection.SendSync(NetFrameNumber, OrderIO.SerializeSync(0, 0));
|
||||
|
||||
if (generateSyncReport)
|
||||
using (new PerfSample("sync_report"))
|
||||
@@ -209,8 +221,7 @@ namespace OpenRA.Network
|
||||
public void Dispose()
|
||||
{
|
||||
disposed = true;
|
||||
if (Connection != null)
|
||||
Connection.Dispose();
|
||||
Connection?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Network
|
||||
{
|
||||
@@ -22,7 +22,7 @@ namespace OpenRA.Network
|
||||
class Chunk
|
||||
{
|
||||
public int Frame;
|
||||
public Pair<int, byte[]>[] Packets;
|
||||
public (int ClientId, byte[] Packet)[] Packets;
|
||||
}
|
||||
|
||||
Queue<Chunk> chunks = new Queue<Chunk>();
|
||||
@@ -32,6 +32,13 @@ namespace OpenRA.Network
|
||||
|
||||
public int LocalClientId { get { return -1; } }
|
||||
public ConnectionState ConnectionState { get { return ConnectionState.Connected; } }
|
||||
public IPEndPoint EndPoint
|
||||
{
|
||||
get { throw new NotSupportedException("A replay connection doesn't have an endpoint"); }
|
||||
}
|
||||
|
||||
public string ErrorMessage { get { return null; } }
|
||||
|
||||
public readonly int TickCount;
|
||||
public readonly int FinalGameTick;
|
||||
public readonly bool IsValid;
|
||||
@@ -47,29 +54,26 @@ namespace OpenRA.Network
|
||||
// to avoid issues with all immediate orders being resolved on the first tick.
|
||||
using (var rs = File.OpenRead(replayFilename))
|
||||
{
|
||||
var packets = new List<Pair<int, byte[]>>();
|
||||
|
||||
var packets = new List<(int ClientId, byte[] Packet)>();
|
||||
var chunk = new Chunk();
|
||||
|
||||
while (rs.Position < rs.Length)
|
||||
{
|
||||
var client = rs.ReadInt32();
|
||||
if (client == ReplayMetadata.MetaStartMarker)
|
||||
break;
|
||||
|
||||
var packetLen = rs.ReadInt32();
|
||||
var packet = rs.ReadBytes(packetLen);
|
||||
var frame = BitConverter.ToInt32(packet, 0);
|
||||
packets.Add(Pair.New(client, packet));
|
||||
packets.Add((client, packet));
|
||||
|
||||
if (frame != int.MaxValue &&
|
||||
(!lastClientsFrame.ContainsKey(client) || frame > lastClientsFrame[client]))
|
||||
if (frame != int.MaxValue && (!lastClientsFrame.ContainsKey(client) || frame > lastClientsFrame[client]))
|
||||
lastClientsFrame[client] = frame;
|
||||
|
||||
if (packet.Length == 5 && packet[4] == (byte)OrderType.Disconnect)
|
||||
continue; // disconnect
|
||||
else if (packet.Length >= 5 && packet[4] == (byte)OrderType.SyncHash)
|
||||
continue; // sync
|
||||
else if (frame == 0)
|
||||
if (packet.Length > 4 && (packet[4] == (byte)OrderType.Disconnect || packet[4] == (byte)OrderType.SyncHash))
|
||||
continue;
|
||||
|
||||
if (frame == 0)
|
||||
{
|
||||
// Parse replay metadata from orders stream
|
||||
var orders = packet.ToOrderList(null);
|
||||
@@ -103,13 +107,13 @@ namespace OpenRA.Network
|
||||
{
|
||||
foreach (var tmpPacketPair in tmpChunk.Packets)
|
||||
{
|
||||
var client = tmpPacketPair.First;
|
||||
var client = tmpPacketPair.ClientId;
|
||||
|
||||
// Don't replace the final disconnection packet - we still want this to end the replay.
|
||||
if (client == lastClientToDisconnect)
|
||||
continue;
|
||||
|
||||
var packet = tmpPacketPair.Second;
|
||||
var packet = tmpPacketPair.Packet;
|
||||
if (packet.Length == 5 && packet[4] == (byte)OrderType.Disconnect)
|
||||
{
|
||||
var lastClientFrame = lastClientsFrame[client];
|
||||
@@ -148,7 +152,7 @@ namespace OpenRA.Network
|
||||
|
||||
while (chunks.Count != 0 && chunks.Peek().Frame <= ordersFrame)
|
||||
foreach (var o in chunks.Dequeue().Packets)
|
||||
packetFn(o.First, o.Second);
|
||||
packetFn(o.ClientId, o.Packet);
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
@@ -25,9 +26,7 @@ namespace OpenRA.Network
|
||||
|
||||
static bool IsGameStart(byte[] data)
|
||||
{
|
||||
if (data.Length == 5 && data[4] == (byte)OrderType.Disconnect)
|
||||
return false;
|
||||
if (data.Length >= 5 && data[4] == (byte)OrderType.SyncHash)
|
||||
if (data.Length > 4 && (data[4] == (byte)OrderType.Disconnect || data[4] == (byte)OrderType.SyncHash))
|
||||
return false;
|
||||
|
||||
var frame = BitConverter.ToInt32(data, 0);
|
||||
@@ -45,7 +44,7 @@ namespace OpenRA.Network
|
||||
{
|
||||
var filename = chooseFilename();
|
||||
var mod = Game.ModData.Manifest;
|
||||
var dir = Platform.ResolvePath(Platform.SupportDirPrefix, "Replays", mod.Id, mod.Metadata.Version);
|
||||
var dir = Path.Combine(Platform.SupportDir, "Replays", mod.Id, mod.Metadata.Version);
|
||||
|
||||
if (!Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
@@ -85,6 +84,14 @@ namespace OpenRA.Network
|
||||
writer.Write(data);
|
||||
}
|
||||
|
||||
public void ReceiveFrame(int clientID, int frame, byte[] data)
|
||||
{
|
||||
var ms = new MemoryStream(4 + data.Length);
|
||||
ms.WriteArray(BitConverter.GetBytes(frame));
|
||||
ms.WriteArray(data);
|
||||
Receive(clientID, ms.GetBuffer());
|
||||
}
|
||||
|
||||
bool disposed;
|
||||
|
||||
public void Dispose()
|
||||
@@ -100,8 +107,7 @@ namespace OpenRA.Network
|
||||
Metadata.Write(writer);
|
||||
}
|
||||
|
||||
if (preStartBuffer != null)
|
||||
preStartBuffer.Dispose();
|
||||
preStartBuffer?.Dispose();
|
||||
writer.Close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace OpenRA.Network
|
||||
// Keyed by the PlayerReference id that the slot corresponds to
|
||||
public Dictionary<string, Slot> Slots = new Dictionary<string, Slot>();
|
||||
|
||||
public HashSet<int> DisabledSpawnPoints = new HashSet<int>();
|
||||
|
||||
public Global GlobalSettings = new Global();
|
||||
|
||||
public static string AnonymizeIP(IPAddress ip)
|
||||
@@ -69,6 +71,9 @@ namespace OpenRA.Network
|
||||
var s = Slot.Deserialize(node.Value);
|
||||
session.Slots.Add(s.PlayerReference, s);
|
||||
break;
|
||||
case "DisabledSpawnPoints":
|
||||
session.DisabledSpawnPoints = FieldLoader.GetValue<HashSet<int>>("DisabledSpawnPoints", node.Value.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +144,7 @@ namespace OpenRA.Network
|
||||
|
||||
public ClientState State = ClientState.Invalid;
|
||||
public int Team;
|
||||
public int Handicap;
|
||||
public string Slot; // Slot ID, or null for observer
|
||||
public string Bot; // Bot type, null for real clients
|
||||
public int BotControllerClientIndex; // who added the bot to the slot
|
||||
@@ -188,6 +194,7 @@ namespace OpenRA.Network
|
||||
public bool LockFaction;
|
||||
public bool LockColor;
|
||||
public bool LockTeam;
|
||||
public bool LockHandicap;
|
||||
public bool LockSpawn;
|
||||
public bool Required;
|
||||
|
||||
@@ -250,8 +257,7 @@ namespace OpenRA.Network
|
||||
|
||||
public bool OptionOrDefault(string id, bool def)
|
||||
{
|
||||
LobbyOptionState option;
|
||||
if (LobbyOptions.TryGetValue(id, out option))
|
||||
if (LobbyOptions.TryGetValue(id, out var option))
|
||||
return option.IsEnabled;
|
||||
|
||||
return def;
|
||||
@@ -259,8 +265,7 @@ namespace OpenRA.Network
|
||||
|
||||
public string OptionOrDefault(string id, string def)
|
||||
{
|
||||
LobbyOptionState option;
|
||||
if (LobbyOptions.TryGetValue(id, out option))
|
||||
if (LobbyOptions.TryGetValue(id, out var option))
|
||||
return option.Value;
|
||||
|
||||
return def;
|
||||
@@ -269,7 +274,10 @@ namespace OpenRA.Network
|
||||
|
||||
public string Serialize()
|
||||
{
|
||||
var sessionData = new List<MiniYamlNode>();
|
||||
var sessionData = new List<MiniYamlNode>()
|
||||
{
|
||||
new MiniYamlNode("DisabledSpawnPoints", FieldSaver.FormatValue(DisabledSpawnPoints))
|
||||
};
|
||||
|
||||
foreach (var client in Clients)
|
||||
sessionData.Add(client.Serialize());
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace OpenRA.Network
|
||||
readonly Report[] syncReports = new Report[NumSyncReports];
|
||||
int curIndex = 0;
|
||||
|
||||
static Pair<string[], Values> DumpSyncTrait(ISync sync)
|
||||
static (string[] Names, Values Values) DumpSyncTrait(ISync sync)
|
||||
{
|
||||
var type = sync.GetType();
|
||||
TypeInfo typeInfo;
|
||||
@@ -41,7 +41,7 @@ namespace OpenRA.Network
|
||||
foreach (var func in typeInfo.SerializableCopyOfMemberFunctions)
|
||||
values[index++] = func(sync);
|
||||
|
||||
return Pair.New(typeInfo.Names, values);
|
||||
return (typeInfo.Names, values);
|
||||
}
|
||||
|
||||
public SyncReport(OrderManager orderManager)
|
||||
@@ -120,9 +120,9 @@ namespace OpenRA.Network
|
||||
Log.Write("sync", "\t {0} {1} {2} {3} ({4})".F(a.ActorID, a.Type, a.Owner, a.Trait, a.Hash));
|
||||
|
||||
var nvp = a.NamesValues;
|
||||
for (int i = 0; i < nvp.First.Length; i++)
|
||||
if (nvp.Second[i] != null)
|
||||
Log.Write("sync", "\t\t {0}: {1}".F(nvp.First[i], nvp.Second[i]));
|
||||
for (int i = 0; i < nvp.Names.Length; i++)
|
||||
if (nvp.Values[i] != null)
|
||||
Log.Write("sync", "\t\t {0}: {1}".F(nvp.Names[i], nvp.Values[i]));
|
||||
}
|
||||
|
||||
Log.Write("sync", "Synced Effects:");
|
||||
@@ -131,9 +131,9 @@ namespace OpenRA.Network
|
||||
Log.Write("sync", "\t {0} ({1})", e.Name, e.Hash);
|
||||
|
||||
var nvp = e.NamesValues;
|
||||
for (int i = 0; i < nvp.First.Length; i++)
|
||||
if (nvp.Second[i] != null)
|
||||
Log.Write("sync", "\t\t {0}: {1}".F(nvp.First[i], nvp.Second[i]));
|
||||
for (int i = 0; i < nvp.Names.Length; i++)
|
||||
if (nvp.Values[i] != null)
|
||||
Log.Write("sync", "\t\t {0}: {1}".F(nvp.Names[i], nvp.Values[i]));
|
||||
}
|
||||
|
||||
Log.Write("sync", "Orders Issued:");
|
||||
@@ -163,14 +163,14 @@ namespace OpenRA.Network
|
||||
public string Owner;
|
||||
public string Trait;
|
||||
public int Hash;
|
||||
public Pair<string[], Values> NamesValues;
|
||||
public (string[] Names, Values Values) NamesValues;
|
||||
}
|
||||
|
||||
struct EffectReport
|
||||
{
|
||||
public string Name;
|
||||
public int Hash;
|
||||
public Pair<string[], Values> NamesValues;
|
||||
public (string[] Names, Values Values) NamesValues;
|
||||
}
|
||||
|
||||
struct TypeInfo
|
||||
|
||||
@@ -9,10 +9,8 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Server;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -29,13 +27,6 @@ namespace OpenRA.Network
|
||||
|
||||
internal static void ProcessOrder(OrderManager orderManager, World world, int clientId, Order order)
|
||||
{
|
||||
if (world != null)
|
||||
{
|
||||
if (!world.WorldActor.TraitsImplementing<IValidateOrder>().All(vo =>
|
||||
vo.OrderValidation(orderManager, world, clientId, order)))
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order.OrderString)
|
||||
{
|
||||
// Server message
|
||||
@@ -48,7 +39,13 @@ namespace OpenRA.Network
|
||||
{
|
||||
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
|
||||
if (client != null)
|
||||
{
|
||||
client.State = Session.ClientState.Disconnected;
|
||||
var player = world?.FindPlayerByClient(client);
|
||||
if (player != null)
|
||||
world.OnPlayerDisconnected(player);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -147,8 +144,7 @@ namespace OpenRA.Network
|
||||
var data = MiniYaml.FromString(order.TargetString)[0];
|
||||
var traitIndex = int.Parse(data.Key);
|
||||
|
||||
if (world != null)
|
||||
world.AddGameSaveTraitData(traitIndex, data.Value);
|
||||
world?.AddGameSaveTraitData(traitIndex, data.Value);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -192,9 +188,8 @@ namespace OpenRA.Network
|
||||
var request = HandshakeRequest.Deserialize(order.TargetString);
|
||||
|
||||
var externalKey = ExternalMod.MakeKey(request.Mod, request.Version);
|
||||
ExternalMod external;
|
||||
if ((request.Mod != mod.Id || request.Version != mod.Metadata.Version)
|
||||
&& Game.ExternalMods.TryGetValue(externalKey, out external))
|
||||
if ((request.Mod != mod.Id || request.Version != mod.Metadata.Version) &&
|
||||
Game.ExternalMods.TryGetValue(externalKey, out var external))
|
||||
{
|
||||
// The ConnectionFailedLogic will prompt the user to switch mods
|
||||
orderManager.ServerExternalMod = external;
|
||||
@@ -337,17 +332,27 @@ namespace OpenRA.Network
|
||||
|
||||
default:
|
||||
{
|
||||
ResolveOrder(order);
|
||||
if (world == null)
|
||||
break;
|
||||
|
||||
if (order.GroupedActors == null)
|
||||
ResolveOrder(order, world, orderManager, clientId);
|
||||
else
|
||||
foreach (var subject in order.GroupedActors)
|
||||
ResolveOrder(Order.FromGroupedOrder(order, subject), world, orderManager, clientId);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ResolveOrder(Order order)
|
||||
static void ResolveOrder(Order order, World world, OrderManager orderManager, int clientId)
|
||||
{
|
||||
if (order.Subject != null && !order.Subject.IsDead)
|
||||
foreach (var t in order.Subject.TraitsImplementing<IResolveOrder>())
|
||||
t.ResolveOrder(order.Subject, order);
|
||||
if (order.Subject == null || order.Subject.IsDead)
|
||||
return;
|
||||
|
||||
if (world.OrderValidators.All(vo => vo.OrderValidation(orderManager, world, clientId, order)))
|
||||
order.Subject.ResolveOrder(order);
|
||||
}
|
||||
|
||||
static void SetOrderLag(OrderManager o)
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace OpenRA
|
||||
|
||||
readonly Cache<string, Type> typeCache;
|
||||
readonly Cache<Type, ConstructorInfo> ctorCache;
|
||||
readonly Pair<Assembly, string>[] assemblies;
|
||||
readonly (Assembly Assembly, string Namespace)[] assemblies;
|
||||
|
||||
public ObjectCreator(Manifest manifest, InstalledMods mods)
|
||||
{
|
||||
@@ -48,8 +48,7 @@ namespace OpenRA
|
||||
// We can't check the internal name of the assembly, so we'll work off the data instead
|
||||
var hash = CryptoUtil.SHA1Hash(File.ReadAllBytes(resolvedPath));
|
||||
|
||||
Assembly assembly;
|
||||
if (!ResolvedAssemblies.TryGetValue(hash, out assembly))
|
||||
if (!ResolvedAssemblies.TryGetValue(hash, out var assembly))
|
||||
{
|
||||
assembly = Assembly.LoadFile(resolvedPath);
|
||||
ResolvedAssemblies.Add(hash, assembly);
|
||||
@@ -59,7 +58,7 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
|
||||
assemblies = assemblyList.SelectMany(asm => asm.GetNamespaces().Select(ns => Pair.New(asm, ns))).ToArray();
|
||||
assemblies = assemblyList.SelectMany(asm => asm.GetNamespaces().Select(ns => (asm, ns))).ToArray();
|
||||
}
|
||||
|
||||
Assembly ResolveAssembly(object sender, ResolveEventArgs e)
|
||||
@@ -68,10 +67,7 @@ namespace OpenRA
|
||||
if (a.FullName == e.Name)
|
||||
return a;
|
||||
|
||||
if (assemblies == null)
|
||||
return null;
|
||||
|
||||
return assemblies.Select(a => a.First).FirstOrDefault(a => a.FullName == e.Name);
|
||||
return assemblies?.Select(a => a.Assembly).FirstOrDefault(a => a.FullName == e.Name);
|
||||
}
|
||||
|
||||
// Only used by the linter to prevent exceptions from being thrown during a lint run
|
||||
@@ -106,7 +102,7 @@ namespace OpenRA
|
||||
public Type FindType(string className)
|
||||
{
|
||||
return assemblies
|
||||
.Select(pair => pair.First.GetType(pair.Second + "." + className, false))
|
||||
.Select(pair => pair.Assembly.GetType(pair.Namespace + "." + className, false))
|
||||
.FirstOrDefault(t => t != null);
|
||||
}
|
||||
|
||||
@@ -146,7 +142,7 @@ namespace OpenRA
|
||||
|
||||
public IEnumerable<Type> GetTypes()
|
||||
{
|
||||
return assemblies.Select(ma => ma.First).Distinct()
|
||||
return assemblies.Select(ma => ma.Assembly).Distinct()
|
||||
.SelectMany(ma => ma.GetTypes());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net461</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Optimize>true</Optimize>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<LangVersion>5</LangVersion>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects>
|
||||
<RootNamespace>OpenRA</RootNamespace>
|
||||
<OutputPath>..</OutputPath>
|
||||
<OutputPath>../bin</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ExternalConsole>false</ExternalConsole>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
<CodeAnalysisRuleSet>..\OpenRA.ruleset</CodeAnalysisRuleSet>
|
||||
<Configurations>Release;Debug;Release-x86</Configurations>
|
||||
<ManagedExeLauncher Condition="('$(OS)' != 'Windows_NT')">mono </ManagedExeLauncher>
|
||||
<Configurations>Release;Debug</Configurations>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Windows'))">win-x64</TargetPlatform>
|
||||
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Linux'))">linux-x64</TargetPlatform>
|
||||
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('OSX'))">osx-x64</TargetPlatform>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Work around an issue where Rider does not detect files in the project root using the default glob -->
|
||||
@@ -27,48 +31,13 @@
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release-x86'">
|
||||
<Prefer32bit>true</Prefer32bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(RunConfiguration)' == 'Red Alert'">
|
||||
<StartAction>Project</StartAction>
|
||||
<StartArguments>Game.Mod=ra</StartArguments>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(RunConfiguration)' == 'Dune 2000'">
|
||||
<StartAction>Project</StartAction>
|
||||
<StartArguments>Game.Mod=d2k</StartArguments>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(RunConfiguration)' == 'Tiberian Dawn'">
|
||||
<StartAction>Project</StartAction>
|
||||
<StartArguments>Game.Mod=cnc</StartArguments>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(RunConfiguration)' == 'Tiberian Sun'">
|
||||
<StartAction>Project</StartAction>
|
||||
<StartArguments>Game.Mod=ts</StartArguments>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Open.Nat">
|
||||
<HintPath>..\thirdparty\download\Open.Nat.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Eluant">
|
||||
<HintPath>..\thirdparty\download\Eluant.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="ICSharpCode.SharpZipLib">
|
||||
<HintPath>..\thirdparty\download\ICSharpCode.SharpZipLib.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="MaxMind.Db">
|
||||
<HintPath>..\thirdparty\download\MaxMind.Db.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<ProjectReference Include="..\OpenRA.PostProcess\OpenRA.PostProcess.csproj">
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
<None Include="App.config" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||
<PackageReference Include="OpenRA-Eluant" Version="1.0.17" />
|
||||
<PackageReference Include="OpenRA-Open.NAT" Version="1.0.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.2.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
|
||||
<AdditionalFiles Include="../stylecop.json" />
|
||||
<AdditionalFiles Include="Properties/launchSettings.json" />
|
||||
</ItemGroup>
|
||||
<Target Name="DisableAnalyzers" BeforeTargets="CoreCompile" Condition="'$(Configuration)'=='Release'">
|
||||
<!-- Disable code style analysis on Release builds to improve compile-time performance -->
|
||||
@@ -76,8 +45,4 @@
|
||||
<Analyzer Remove="@(Analyzer)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
<Target Name="PostProcess" AfterTargets="Build" Inputs="$(TargetPath)" Outputs="$(IntermediateOutputPath)\$(TargetFileName).processed">
|
||||
<Exec Command="$(ManagedExeLauncher)"$(TargetDir)OpenRA.PostProcess.exe" "$(TargetPath)" -LAA" />
|
||||
<Touch Files="$(IntermediateOutputPath)\$(TargetFileName).processed" AlwaysCreate="true" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Graphics;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Orders
|
||||
@@ -18,13 +18,14 @@ namespace OpenRA.Orders
|
||||
public class GenericSelectTarget : UnitOrderGenerator
|
||||
{
|
||||
public readonly string OrderName;
|
||||
protected readonly IEnumerable<Actor> Subjects;
|
||||
protected readonly string Cursor;
|
||||
protected readonly MouseButton ExpectedButton;
|
||||
|
||||
protected IEnumerable<Actor> subjects;
|
||||
|
||||
public GenericSelectTarget(IEnumerable<Actor> subjects, string order, string cursor, MouseButton button)
|
||||
{
|
||||
Subjects = subjects;
|
||||
this.subjects = subjects;
|
||||
OrderName = order;
|
||||
Cursor = cursor;
|
||||
ExpectedButton = button;
|
||||
@@ -53,8 +54,7 @@ namespace OpenRA.Orders
|
||||
world.CancelInputMode();
|
||||
|
||||
var queued = mi.Modifiers.HasModifier(Modifiers.Shift);
|
||||
foreach (var subject in Subjects)
|
||||
yield return new Order(OrderName, subject, Target.FromCell(world, cell), queued);
|
||||
yield return new Order(OrderName, null, Target.FromCell(world, cell), queued, null, subjects.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,11 @@ namespace OpenRA.Orders
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void SelectionChanged(World world, IEnumerable<Actor> selected)
|
||||
{
|
||||
subjects = selected;
|
||||
}
|
||||
|
||||
public override bool ClearSelectionOnLeftClick { get { return false; } }
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user